Formik: passing form errors from outside

Created on 4 Jul 2017  路  37Comments  路  Source: formium/formik

Hi :)

What's the best way to pass the errors from outside?
What I'm trying to do is to get network errors from the redux store.

Most helpful comment

In theory, you can pass whatever functions you want to any action creator and then do whatever from there.

Example:

// myform.js
...
const withFormik = Formik({
...
  handleSubmit(payload, {props, setErrors, setSubmitting}) {
     props.myThunkActionCreator(payload, setErrors, setSubmitting)
  }
})

...

``js // actions.js export const myThunkActionCreator = (payload, setErrors, setSubmitting) => dispatch => { // Yay! Can invoke sync or async actions withdispatch`
dispatch({ type: 'START' });
doSomethingAsync(payload)
.then(
res => {
dispatch({ type: 'SUCCESS' : payload: res })
setSubmitting(false)
},
error => {
dispatch({type: 'FAILURE', payload: error, error: true })
setSubmitting(false)
setErrors(transformErrorsFromMyApi(error))
}
)
}

All 37 comments

Just use props :-)

Oh, I thought there was a way to use the error props from formik! Thanks :)

Sorry are you trying to send form errors from Formik to redux or errors from redux to formik?

Formik has a wild card setError: (err: any) => void which allows you to arbitrarily set props.error to whatever you want (it is just this.setState internally).

redux to formik :)

I saw setError, but it would need to be called in the component lifecycle, not in the formik options (it might make sense to allow to pass additional errors like we do for the values in mapPropsToValues)

Would you be able to explain more about you use case?

mapPropsToValues transforms props into formik component state. If your error is coming from redux, redux handles that state already, so there is no need to map it to formik. You should just pass it in as a prop and fire it off when you need to.

well, I just wanted to have the errors coming from the server inside the errors prop that is passed by formik :)

I agree that this would be a nice feature. When submitting through sagas, we get the response sometime later, and it would be great to react on that in Formik.

  • Submit Formik
  • Saga response to action
  • Fetch return with error
  • Selector passes in error as props to container => Formik response to this error

Is it possible to setError from outside?

In theory, you can pass whatever functions you want to any action creator and then do whatever from there.

Example:

// myform.js
...
const withFormik = Formik({
...
  handleSubmit(payload, {props, setErrors, setSubmitting}) {
     props.myThunkActionCreator(payload, setErrors, setSubmitting)
  }
})

...

``js // actions.js export const myThunkActionCreator = (payload, setErrors, setSubmitting) => dispatch => { // Yay! Can invoke sync or async actions withdispatch`
dispatch({ type: 'START' });
doSomethingAsync(payload)
.then(
res => {
dispatch({ type: 'SUCCESS' : payload: res })
setSubmitting(false)
},
error => {
dispatch({type: 'FAILURE', payload: error, error: true })
setSubmitting(false)
setErrors(transformErrorsFromMyApi(error))
}
)
}

Late reply here... this is my approach. Sorry for any mistakes, this is copy paste, with unrelated code removed (might not compile, probably won't)

// @flow

import React from 'react';
import { connect } from 'react-redux';

import { selectIsRequestRunning, selectRequestError } from '../redux/selectors';
import { updateProfileAction } from '../redux/ducks/user';
import { Form } from '../hoc';

@connect(s => ({
  requestIsRunning: selectIsRequestRunning(s, 'PROFILE_UPDATE_REQUEST'),
  requestError: selectRequestError(s, 'PROFILE_UPDATE_REQUEST'),
}))
@Form({
  mapPropsToValues: props => ({
    // fields...
  }),
  handleSubmit: (values, { props, setErrors, setSubmitting }) => {
    // 1. trigger action to update user profile
    this.props.dispatch(updateProfileAction(values));

    // 2. saga (in my case) runs network request. Is sets `isRunning` or `errors` flags for request based on results of request.
    // Example of such saga
    // const workerUpdateProfile = function*(action: VerifyCredentialsAction) {
    //   yield put(startRequest('PROFILE_UPDATE_REQUEST'));
    //   try {
    //     const accessToken = yield call(API.users.updateProfile, action.payload);
    //     yield put(endRequest('PROFILE_UPDATE_REQUEST'));
    //   } catch (error) {
    //     yield put(endRequestWithError('PROFILE_UPDATE_REQUEST', error));
    //   }
    // };

    // export const saga = function*(): Generator<*, *, *> {
    //   yield takeLatest('user/UPDATE_PROFILE', workerUpdateProfile);
    // };

    // 3. watch for this change in `componentWillReceiveProps` and react (see componentWillReceiveProps below)
  },
})
export default class UserForm extends React.Component<*, *, *> {
  componentWillReceiveProps(nextProps) {
    this.props.setSubmitting(nextProps.requestIsRunning);
    this.props.setErrors(nextProps.requestError);
  }

  render() {
    const {
      values,
      touched,
      errors,
      dirty,
      isSubmitting,
      setFieldValue,
      setFieldTouched,
      handleSubmit,
      handleReset,
      setSubmitting,
      setErrors,
    } = this.props;

    return <View onSubmit={handleSubmit}>{/*fields*/}</View>;
  }
}

@Andreyco That's awesome. What other UI needs to see submitting state outside of the form??

@Andreyco I def see the benefit using CWRP instead of passing setSubmitting and setErrors if you are using that thunk or saga elsewhere

Loaders, basically. Also, in mobile apps you often need to display "loading" indicator in status bar - dummy solution is to implement "reference counter" which display that spinner when running request > 0.

68747470733a2f2f7261772e6769746875622e636f6d2f7069656d6f6e74652f50424a4163746976697479496e64696361746f722f6d61737465722f50424a4163746976697479496e64696361746f722e676966

Sorry, I missed a word and misunderstood your last comment. You are totally correct here, you do not have to worry about complications related to reusing saga/thunk in other places.

I'm running into an issue when implementing @Andreyco 's method.

Specifically, I'm getting:

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

When running:

import * as React from 'react';
import { FormikProps, Form, Field, FieldProps, withFormik } from 'formik';

interface LoginValues {
  email: string,
  password: string
}

interface LoginFormProps {
  errors: {
    email: string | null,
    password: string | null
  }
};

class InnerForm extends React.Component<FormikProps<LoginValues> & LoginFormProps> {
  componentWillReceiveProps(nextProps: LoginFormProps) {
    this.props.setErrors(nextProps.errors);
  }

  render () {
    return (
      <Form>
        <Field
          name="email"
          render={({ field, form }: FieldProps<LoginValues>) =>
            <div>
              <input type="text" {...field} placeholder="Email" />
              {form.touched.email &&
                form.errors.email &&
                form.errors.email}
            </div>}
        />
        <Field
          name="password"
          render={({ field, form }: FieldProps<LoginValues>) =>
            <div>
              <input type="password" {...field} placeholder="Password" />
              {form.touched.password &&
                form.errors.password &&
                form.errors.password}
            </div>}
        />
        <button type="submit" disabled={this.props.isSubmitting}>
          Submit
        </button>
      </Form>
    );
  }
}

const LoginForm = withFormik<LoginFormProps, LoginValues>({
  handleSubmit: (values: LoginValues) => alert(JSON.stringify(values)),
  mapPropsToValues: (props: LoginFormProps) => {
    return {
      email: '',
      password: ''
    }
  }
})(InnerForm);

export default LoginForm;

Any advice?

Well, since validate takes props, I think I might as well use that instead of CWRP. The following forks fine:

validate: (values: LoginValues, props: LoginFormProps) => {
    // Do this here until Yup has types
    let errors: LoginErrors = {};
    if (!values.email) {
      errors.email = "Email is a required field";
    }
    if (values.password.length < 8) {
      errors.password = "Password must be 8 characters long";
    }
    console.log('errors so far', errors);
    return {
      ...props.errors,
      ...errors
    };
  }

@rclmenezes @Andreyco 's approach means that you get errors from outside of component (e.g redux store). It won't work in your case because you run into infinite loop by setting errors over and over again. Yeah, your solution is absolutely fine.

I'm having a similar issue. I'm also new to React.
Currently, I have a field "code". When user fill-in and press "Enter", I will call an API to a third party system. At some point of time, I would like to display "Code" not valid. I'm using event "onKeyPress" at the moment. "setErrors" does not seem to work to me.

When using with a saga, the error handling felt off using @Andreyco's solution. While yes, I can pass it in on props, I lose the ability to have formik handle the error/submitting state at all. If I want to clear out the error when they begin typing again, I need to pass another dispatch event to clear out my error in the onChange handler. This led to a mess of boilerplate that I was trying to avoid by using a solution like this in the first place. On the other hand, passing functions into my actions to have my saga use feels wrong too, so I'm lost.

I wonder if there should be another option passed into withFormik that allows us to pass in errors from our store and have them be treated as if setErrors() was called, so that it can clear them out that way?

@jaredpalmer @Andreyco there are two things here:
1) CWRP is deprecated
2) passing actions into saga is inverse of control and IMO is not super clear and goes against unidirectional data flow.
I would still consider adding smth like mapPropsToErrors as @patrick91 suggested. I would say that it is quite consistent approach no?

I would also love to see mapping of initial errors via props to formik.
Well we already have API like initialValues, why can't we have initialErrors?

Just want to point out that I think mapPropsToErrors can be implemented using composition with the current APIs. Sagas seems like an edge case that might not be completely coverable. Right now the best method is to access the formik handlers like setErrors inside of a callback inside of handleSubmit. Sagas throw a wrench in this and passing in these handlers (or the whole formikBag) to an action is undesirable.

I don't think mapPropsToErrors is a good idea because then we're headed down a road where we need props for things like mapPropsToStatus, too. If you're currently using sagas I'd recommend building your own Component / HOC that creates this behavior through composition rather than pushing for it to be included in the library directly

@slightlytyler I hadn't considered an HOC to compose that functionality. Would it just set the errors on mount using the formik bag?

@mattoni you could build the same API as mapPropsToValues. Would use componentDidMount and componentDidUpdate

Has anyone created an example of this HOC they could share? Just starting out so it's slightly above my paygrade

I found a solution that can help that using ref of react. So if you can pass the errors props and setErrors with this ref. Hope it help.

<Formik ref={el => (this.form = el)}>{chidren}</Formik>
componentDidUpdate() {
    const { errors } = this.props;

    this.form.setErrors(errors);
  }

I guess that ref is not proper place to store/get acces for errors

from documentation:

The ref is used to return a reference to the element.

@idoo I don't store errors in ref. I using ref for getting formik reference and using setErrors function of formik

For anyone who comes across this looking for a way to handle api errors, this works fairly well with React hooks + Axios + Formik + Rails / ActiveRecord validation error responses:

export default function SomeFormComponent() {
  const formikRef = React.useRef();

  const onSubmit = React.useCalback(async values => {
    try {
      await someFunctionThatUsesAxiosToPost(values);
    } catch (apiException) {
      if (apiException.response && apiException.response.data && apiException.response.data.error) {
        formikRef.current.setErrors(apiException.response.data.error);
      } else {
        console.error(apiException); // or some other fallback handling
      }
    }
  });

  return (
    <Formik onSubmit={onSubmit} ref={formikRef}>
      ...  rest of form ...
  );
}

in reality I moved off that functionality into a custom hook, but the idea is the same. Hopefully this is helpful for someone 馃槃

in react native I think can't use react hooks, can I? so what can I do? I have no idea how to use setError when I use Saga.

@mohamadnabikhani Upgrade to RN 0.59 and use hooks to achieve this with ease!

We use sagas in our production app and set form errors as such in the example below:

// Our internal methods to get network status / errors from redux ->
const { ... errors } = useSelector(state => getNetworkRequestByType(state, REGISTER_REQUEST));
const emailError = _.get(errors, "email", null);

// Code you care about ->>>>>> FORMIK!

const formikRef = useRef(null);

useEffect(() => {
  formikRef.current.setErrors({
     "email": emailError
  })
}, [emailError]);

 <Formik
      ref={formikRef}
      ...

Here is a custom hook for this use-case:

/**
 * Sets a array of errors for formik form via a ref
 *
 * @param errors
 * @param ref - reference to formik component
 * @param map - maps error keys to the correct fields
 */
export function useSetFormikErrors(errors, ref, map){
  useEffect(
    () => {
      /*
       * Maps keys to the correct filed names, e.g. if
       * we supply a map of { firstName: "newFieldName" }
       * the 'firstName' error will be set on the 'newFieldName'
       * field.
       */
      const mappedErrors =  _mapKeys(errors, (value, key) => {
        return _get(map, key, key) || key;
      });

      if(!_isNil(errors)) ref.current.setErrors(mappedErrors);
    },
    [errors]
  );
}

useSetFormikErrors(errors, formikRef, { firstName: "myFieldName" });

Pass an errors object like:

{
   email: "There is an error with email"
}

And supply and optional mapping if needed in third parameter.

This change https://github.com/jaredpalmer/formik/pull/1799 makes errors "re-initializable" from props. So you can just use regular props to pass errors to the form.

I get a property ref doesn't exist on type IntrinsicAttributes & FormikConfig when trying to use refs with a

For anyone who is still confused, here is my implementation with withFormik(). This may be still need to be improved, because I finished it when I feel dizzy, kind of rather sleepy. But it works.

Basically, what I did is set error to formik's status and also boolean flag after user submitting the form which the name is afterSubmit to prevent infinite loop on componentDidUpdate and leveraging the status to store it and manipulate its value using setStatus() as recommended by the formik's author. Also, I set the initial error status using mapPropsToStatus.

Please check out the following codes for more detailed explanation:

Formik pass form error from server

You can check out the complete codes on my github repo, if you need even more detailed explanations. The repo name is mernshop_front.

This change #1799 makes errors "re-initializable" from props. So you can just use regular props to pass errors to the form.

Is this change already deployed to a version? Currently using 2.1.4 but can't find this part of code in it. How to use this fix?

Than you :-)

React Native use innerRef={formikRef}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dearcodes picture dearcodes  路  3Comments

Jucesr picture Jucesr  路  3Comments

jordantrainor picture jordantrainor  路  3Comments

jaredpalmer picture jaredpalmer  路  3Comments

PeerHartmann picture PeerHartmann  路  3Comments