Formik: setSubmitting from props or redux state

Created on 10 Mar 2018  Â·  14Comments  Â·  Source: formium/formik

Question?

Is it better to just not use Formik's isSubmitting if this state is stored elsewhere (redux state).

Current Behavior

<Formik
...
onSubmit={(values) => {
    sendInvite(values.email);
}}

My handleSubmit calls a redux action and I'm selecting isSubmitting via the redux state.

Desired Behavior

<Formik
...
submitting={submitting}
formikWillReceiveProps={(props, nextProps, {setSubmitting}) => {
    if (props.submitting && !nextProps.submitting) {
        setSubmitting(false);
    }
}}/>

Additional Information

Is this https://github.com/jaredpalmer/formik/issues/401?


  • Formik Version: 0.11.11

Most helpful comment

I've just started using Formik on a project with redux-saga and so far I'm ignoring the isSubmitting prop and using the request status from my redux store to control things like the spinner. Are there any caveats I should be aware of by ignoring the built in isSubmitting?

I agree that there should be a single source of truth for this piece of state, but in a redux app I don't think that's the responsibility of the UI code. Is there a recommended way for using externally supplied state to drive the Formik isSubmitting state?

All 14 comments

that would be an example of how 401 would be “abused” for side effects.

Formik keeps state internally and is not a a controlled component. What you have described is turning Formik into a such a component and thus defeating the main benefits. My suggestion is to not try to keep state in two places and let Formik be the source of truth when it comes to submission state

Thanks, I've resolved this. I usually call redux actions and not api services directly, but thankfully redux-thunk allows these actions to return a promise that I can use for toggling setSubmitting.

@deanbot thank for your suggestion 👍

Regarding this, does anybody know of a good solution for formik and redux-saga?

I found this repo: https://github.com/bfillmer/formik-saga but it seems to be an anti-pattern since it's calling Formik actions inside the logic part (Redux) instead of the view part (React component).

I've just started using Formik on a project with redux-saga and so far I'm ignoring the isSubmitting prop and using the request status from my redux store to control things like the spinner. Are there any caveats I should be aware of by ignoring the built in isSubmitting?

I agree that there should be a single source of truth for this piece of state, but in a redux app I don't think that's the responsibility of the UI code. Is there a recommended way for using externally supplied state to drive the Formik isSubmitting state?

I'm using this: https://github.com/erikras/redux-promise-listener along with https://github.com/erikras/react-redux-promise-listener to get around this. So far it seems to be working well.

I thought I would share a solution to the Formik and Saga's question.
Formik passes down set functions, i.e. setStatus to children, but there is no clear way to call this, short of overriding Formik "componentDidUpdate". Since that isn't possible or a good idea, I created another component with just this purpose.

          <Formik
            initialValues={{
              email: '',
              password: '',
            }}
            validationSchema={this.loginSchema}
            onSubmit={(values, actions) => {
              this.handleSubmit(values, actions)
            }}
            auth_status={this.props.auth_status}
          >
            {({errors, touched, status, setStatus}) => (
              <div>
                {status ?
                  <div className="all-error errorlist">
                    {status}
                  </div>
                  : ''
                }
                <Form>
                  <div>
                    <label htmlFor="id_email">Email</label>
                    <Field type="email" name="email"/>
                    <ErrorMessage name="email" component={formErrorMessage}/>
                  </div>
                  <div>
                    <label htmlFor="id_password">Password</label>
                    <Field type="password" name="password"/>
                    <ErrorMessage name="password" component={formErrorMessage}/>
                  </div>
                  <button type="submit" className="form-submit-orange">Login</button>
                </Form>
                <FormikReduxInterface
                  auth_status={this.props.auth_status}
                  actions={{setStatus: setStatus}}
                />
              </div>
            )}
          </Formik>

FormikReduxInterface handles compoentWillUpdate, and if the props are changed, it will call setStatus with any errors text.

@dabrowne I think what you're doing _is_ the best way to go.

We aren't obligated to use Formik's state management--we can opt in for whatever we like and not use whatever we don't like. In the case of submit using redux, just don't use Formik's management there and in the submit state as a prop.

EDIT: To illustrate more clearly, here's a code snippet:

const MyForm = (props) => (
  <Formik
    onSubmit={this.props.onSubmit}
    render{({...}) => (
      <form>
        <button disabled={props.isSubmitting}>Submit not tied to Formik state, i.e. controlled submit state</button>
      </form>
    )
  />
)

I agree with @flushentittypacket. This is fine as long as you are consistent in not using isSubmitting and fully understand the consequences. isSubmitting means “is submit attempt or in progress” so it is true prior to full form validation and false after submit has completed. This is meant to be used to disable buttons that prevent submissions—so you can block another submit _attempt_ if another is in progress. With that in mind you may want to disable buttons based on isSubmitting && props.reduxSubmissionIsLoading

With that in mind you may want to disable buttons based on isSubmitting && props.reduxSubmissionIsLoading

Did you mean isSubmitting || props.reduxSubmissionIsLoading?

Anyway, if we refer to isSubmitting, I think we're back to the problem of how to call setSubmitting(false) when props.reduxSubmissionIsLoading is false

I was under the impression that Formik validation and submission would be blocked whilst isSubmitting is true, thus preventing us from relying on props.reduxSubmissionIsLoading, however it seems this is not the case. So my best guess that it is completely safe to just rely on props.reduxSubmissionIsLoading and no need to touch Formik setSubmitting.

I created the following project to handle the submit state with redux/redux-saga. I don't know if you still need this but here it goes:

formik-redux

This is my implementation to auto set isSubmitting to false using a wrapped HOC. I chose to do it this way as I don't call a promise inside of my component, its all handled through middlewares.

import React from 'react';
import { withFormik } from 'formik';
import { connect } from 'react-redux';
import { actions } from 'app/uiReducer';

const mapDispatchToProps = (dispatch) => ({
  startSubmitting: () => dispatch(actions.startSubmittingForm()),
});

const mapStateToProps = (state) => ({
  reduxSubmitting: state.ui.forms.isSubmitting,
});

/**
 * This HOC wraps the withFormik HOC and hooks it into our redux state.
 * When submit is clicked, a startSubmission action will be dispatched.
 * When the promise resolves for the submission, stopSubmission action
 * is dispatched by the API layer.
 * This HOC will auto reanable the form if a submission errors out.
 *
 * More reference material:
 * https://jaredpalmer.com/formik/docs/guides/form-submission
 * https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
 * https://github.com/jaredpalmer/formik/issues/502
 *
 */
const withReduxFormik = (formikConfig) => (Component) => {
  return connect(mapStateToProps, mapDispatchToProps)(withFormik(formikConfig)((props) => {

    // Need a ref to track submission state internally as we can't hook onto the pre submission code.
    // The ref also does not respond to renders.
    const submittingState = React.useRef(false);

    React.useEffect(() => {
      const {
        //from formik
        isSubmitting,
        setSubmitting,
        // from redux, need redux state so we know when the for submission completes.
        reduxSubmitting,
      } = props;
      if (isSubmitting && !reduxSubmitting && submittingState.current === false) {
        submittingState.current = true;
        props.startSubmitting();
      } else if (isSubmitting && !reduxSubmitting && submittingState.current === true) {
        submittingState.current = false;
        setSubmitting(false);
      }
    }, [props.isSubmitting, props.reduxSubmitting]);

    return <Component {...props} />;
  }));
};

export default withReduxFormik;

// New actions:
  SUBMITTING_FORM_START: 'UI/SUBMITTING_FORM_START',
  SUBMITTING_FORM_STOP: 'UI/SUBMITTING_FORM_STOP',
  // called inside Wrapped HOC
  startSubmittingForm: () => ({ type: types.SUBMITTING_FORM_START }),

 // dispatched where you handle your asynciness.
  stopSubmittingForm: () => ({ type: types.SUBMITTING_FORM_STOP }),

// New reducer:
import { types } from './actionTypes';

const defaultState = {
  isSubmitting: false,
};

export const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case types.SUBMITTING_FORM_START:
      return {
        ...state,
        isSubmitting: true,
      };
    case types.SUBMITTING_FORM_STOP:
      return {
        ...state,
        isSubmitting: false,
      };
    default:
      return state;
  }
};

export default reducer;

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jaredpalmer picture jaredpalmer  Â·  3Comments

sibelius picture sibelius  Â·  3Comments

dfee picture dfee  Â·  3Comments

jaredpalmer picture jaredpalmer  Â·  3Comments

green-pickle picture green-pickle  Â·  3Comments