Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to integrate redux-form's onSubmit with redux-api-middleware?

I'm writing a React / Redux app using redux-form and redux-api-middleware, and I'm having trouble integrating redux-form's onSubmit function with the RSAA lifecycle.

The redux-form documentation says that the onSubmit handler should return a Promise. Until resolve is called on the promise, the form's submitting prop will be true.

However, in this app my API calls don't currently use promises (e.g. via fetch). I make API calls by dispatching a [CALL_API] RSAA action and reducing redux-api-middleware's response actions.

Problem code

class MyReduxFormContainer extends Component {
  render() {
    return (
      <MyReduxForm submit={this.props.submit} />
    )
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    submit: function(values, dispatch) {
      dispatch({
        [CALL_API]: {
          method: 'PATCH',
          types: [
            {
              type: 'REQUEST',
              endpoint: '...',
              body: JSON.stringify(values)
            },
            'SUCCESS',
            'FAILURE'
          ]
        }
      });
      // Problem: redux-api-middleware-style API calls normally don't leverage promises.
      // Out of the box, this library doesn't offer a promise to return.
    }
  }
};

export default connect(
  // ...
  mapDispatchToProps
)(MyReduxFormContainer)

Possible Solutions

I could pass a promise through the payload RSAA callback, which could then resolve/reject the promise after the API response, but this seems to violate the rule that "action creators should't cause side-effects." Granting that redux-api-middleware seems to bend this rule.

I could in theory just use fetch inside the onSubmit handler, instead of redux-api-middleware, but this isn't just a concession which makes my API interactions inconsistent across the application, it also risks duplicating any API middleware activities I've baked in, e.g. setting default headers, de-camelizing / camelizing payloads, etc.

Does anyone have experience using redux-form and redux-api-middleware together?

If it were just redux-api-middleware, I would have expected to simply change the form's submitting prop by altering the form's state when reducing the ACTION_TYPE_[REQUEST|SUCCESS|FAILURE] action types. But it seems non-standard and potentially risky to directly modify the form's state from a reducer. Example redux-form implementations seem to emphasize that redux-form state should be transparent / only indirectly manipulated.

Any thoughts / pointers would be greatly appreciated!

Related GitHub issues

redux-api-middleware:

  • https://github.com/agraboso/redux-api-middleware/issues/21
  • https://github.com/agraboso/redux-api-middleware/issues/53

redux-form:

  • https://github.com/erikras/redux-form/issues/777
like image 604
Daniel B. Avatar asked Aug 03 '16 15:08

Daniel B.


2 Answers

Recently I found quite elegant and generic way combine it. Here is article with explanation

export const formToApiAdapter = (dispatch, actionCreator, formatErrors) => (
  (...args) => (
    new Promise((resolve, reject) => (
      dispatch(actionCreator(...args)).then(
        (response) => {
          if (response.error) {
            return reject(formatErrors(response));
          }
          return resolve(response);
        }
      )
    ))
  )
);
like image 192
Artur Charaev Avatar answered Jan 02 '23 11:01

Artur Charaev


For lack of a better solution, I'm currently wrapping my dispatch({[CALL_API]}) call inside of a Promise, within the redux-form submit handler.

class MyReduxFormContainer extends Component {
  render() {
    return (
      <MyReduxForm submit={this.props.submit} />
    )
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    submit: function(values, dispatch) {

      // Solution: Wrap the [CALL_API] dispatch in a Promise
      return new Promise((resolve, reject) => {

        dispatch({
          [CALL_API]: {
            method: 'PATCH',
            types: [
              {
                type: 'MY_PATCH_REQUEST'
                endpoint: '...',
                body: JSON.stringify(values)
              },
              {
                type: 'MY_PATCH_SUCCESS',
                payload: function (action, state, res) {

                  // Solution: resolve() the promise in the SUCCESS payload callback
                  // Changes `submitting` prop of MyReduxForm
                  resolve();

                }
              },
              {
                type: 'MY_PATCH_FAILURE',
                payload: function (action, state, res) {

                  // Solution: reject() the promise in the FAILURE payload callback
                  // Changes `submitting` prop of MyReduxForm
                  reject();

                }
              }
            ]
          }
        });

      });

    }
  }
};

export default connect(
  // ...
  mapDispatchToProps
)(MyReduxFormContainer)

Ultimately I'm pretty unhappy with this code architecture, and at this point I think standard fetch usage would have been preferable to redux-api-middleware.

Triggering effects after API responses is standard enough as a concern, there ought to be more elegant solutions than this kind of callback nesting, e.g. using a promise chain.

like image 44
Daniel B. Avatar answered Jan 02 '23 12:01

Daniel B.