Open vladshcherbin opened 7 years ago
2 options:
Write an small function that transforms your backend error into same shape as formik’s errors and then call setErrors
Use status setStatus
@jaredpalmer yes, I transformed backend errors and the fields get them. The problem is that when you change/blur one of fields, all other backend errors are gone since newly triggered validation will erase them.
We use option 1, and pass around a small helper function.
@jaredpalmer here is a codesandbox demo of what I mean.
Set the values of email and username, submit the form and you'll receive 2 backend errors. Now, change the value or blur one of the fields (to trigger validation) - all backend errors will be gone.
Hmmm yeah not ideal.
Need to think about this one.
Possible solution - add submitErrors
object where you store submit errors. This way, submitErrors
won't interact with client-side errors
and can be handled separately.
Currently we handle such errors via setStatus
. In our case such errors don't prevent form from being re-submitted and they're not cleared away even after related field's onBlur/onChange
event.
But in general case, after setting such errors from outside - each field should hold its own errors until onBlur/onChange
event.
upd: it's hard to achieve this with yup validation schema (e.g. f(schema, values) => errors
), since now validation would also depend on props/status f(schema, values, formikStatus) => errors
@VladShcherbin this use case is tough, and I think the best way to deal with this is to actually have two separate feedback components that render error messages for each input. First attempt, show regular error/touched errors. If there are then API errors, keep them in status.errors. Then show those instead during the second attempt. That way, regular validation won’t overwrite them.
@jaredpalmer nah, other libraries can handle this use case.
I'm sure, we'll find a nice way to deal with this too 😉
@amankkg I had the same problem. I use setStatus
to set server side errors, but I wanted those errors to disappear after a given field is changed. Here is my solution:
<Field
type="email"
name="email"
onChange={(v) => {
if (status && status.email) {
const { email, ...rest } = status;
setStatus(rest);
}
handleChange(v);
}}
/>
If this pattern was repeated, you could reuse similar logic in your custom Field
component.
workaround ≠solution This is a rather really common use case and I think Formik could solve this in a much neater way. I think this issue should be reopened.
Feel free to submit PR’s open to exploring this
Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.
yep, instead of fixing/adding things let's just close issues like nothing happened. not even surprised to see this bot here 😄
@vladshcherbin thanks!
@vladshcherbin I wish I had time to go through it all but I don't. So rather than continue to let things pile up, I figured I could make small dent with this bot. If people still need help they will holler (just like you just did!).
This feature is planned in v2 btw #828
Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.
Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.
We were just talking about this today internally.
There are 2 parts to this:
For 2, as described, you can use status
to store the data and then in your render function figure out what to show. FWIW status
used to be called error
(singular) and was intended for this use case.
It's awkward in v1, but will be less so in v2:
function Fieldset({ name }) {
const { status } = useFormikContext()
const [field, meta] = useField(name)
const apiError = getIn(status, name);
return <>
<input {...field} />
{meta.touch && (meta.error || apiError) && <div>{meta.error || apiError}</div>}
</>
}
However, if we implement async submit AND error handling in v2, we would need to introduce a new key so as to not mess with people's existing usage of status
.
Is this possible in v2 now?
@fivethreeo @jaredpalmer wondering this too
I am also wondering if this is possible in v2.
Does anyone have a good workaround for v1, if I am to only care for server-side validation?
I have a workaround by using setFieldError
during handleSubmit
and resetting individual field errors during field's onChange
..
For anyone else coming to this thread and being none the wiser, I found @jaredpalmer gave an answer on SO that solved my wondering how this is done (in v1).
Snippet:
onSubmit={(values, { setSubmitting, setErrors }) => {
const axios = getAxiosInstance();
axios.post('/reset/', {
'email': email
}).then((response) => {
setSubmitting(false);
}).catch((error) => {
setSubmitting(false);
const fieldErrorsFromResponse = error.response.data.field_errors;
// fieldErrorsFromResponse from server has {'field_errors': {'email': 'Invalid'}}
if (fieldErrorsFromResponse !== null){
setErrors(fieldErrorsFromResponse);
}
}
);
}}
render={({ isSubmitting, errors }) => (
<Form className="form-horizontal">
<p>Enter the email address associated with your account, and we'll email you a link to reset your password.</p>
<FormGroup>
<Label htmlFor="username">Email address</Label>
<Field type="email" name="email" />
<ErrorMessage name="email" component="div" className="field-error" />
</FormGroup>
If someone needs a bigger example for copy'n'pasting the idea from @jaredpalmer mentioned in https://github.com/jaredpalmer/formik/issues/150#issuecomment-447147835 here is a gist:
https://gist.github.com/donaldpipowitch/4f3989edb2aadd9e44c2856c65e90b2e
Here you can find two hooks to retrieve and reset server side errors properly. The hooks either look for field specific errors (setStatus({ field: 'Your error message' }
) or global errors (setStatus(true)
).
I made an example that can make life easier for some people. follow the link more this looks like a hack
import { useCallback } from "react";
import { useField as useFieldFormik, useFormikContext, getIn } from "formik";
export function useField(name) {
const { status, setStatus } = useFormikContext();
const [{ onBlur: onBlurFormik, ...field }, meta] = useFieldFormik(name);
const apiError = getIn(status, name);
const onBlurMemo = useCallback(
e => {
setStatus({
...status,
[name]: null
});
onBlurFormik(e);
},
[status, name, setStatus, onBlurFormik]
);
return [{ onBlur: onBlurMemo, ...field }, { ...meta, apiError }];
}
import React from "react";
import { useField } from "./use-field";
function Input(props) {
const [field, meta] = useField(props.name);
return (
<>
<input {...field} {...props} />
{(meta.error || meta.apiError) && (
<div>{meta.error || meta.apiError}</div>
)}
</>
);
}
export default Input;
import React from "react";
import { Formik } from "formik";
import Input from "./Input";
import { request } from "./helpers";
function FormExample() {
return (
<Formik
validate={values => {
let errors = {};
if (!values.email) {
errors.email = "Required by client";
}
if (!values.password) {
errors.email = "Required by client";
}
return errors;
}}
initialValues={{ email: "", password: "" }}
onSubmit={async (values, actions) => {
try {
await request();
} catch (error) {
actions.setStatus(error);
}
}}
>
{props => (
<form onSubmit={props.handleSubmit} autoComplete="off">
<label htmlFor="email" style={{ display: "block" }}>
Email
</label>
<Input
id="email"
name="email"
placeholder="Enter your email"
type="text"
autoComplete="off"
/>
<label htmlFor="password" style={{ display: "block" }}>
Password
</label>
<Input
id="password"
name="password"
placeholder="Enter your password"
type="password"
autoComplete="off"
/>
<button type="submit">Submit</button>
</form>
)}
</Formik>
);
}
export default FormExample;
Anyone know the best way to handle this with sagas? My API is decoupled from the form's submit function, so I can't directly access the errors/response in Formik.onSubmit
.
My form calls a submit
function with the form's values:
<Formik
initialValues={{
email,
password: "",
passwordConfirmation: ""
}}
validationSchema={ValidationSchema}
onSubmit={(values, { setErrors, setSubmitting }) => {
const { password, passwordConfirmation } = values;
submit(token, password, passwordConfirmation);
}}
>
submit
dispatches an action that starts a request:
const mapDispatchToProps = dispatch => ({
submit: (token, password, passwordConfirmation) => {
dispatch(setPasswordRequest({ token, password, passwordConfirmation }));
}
});
There's then a "loading", "success", and "failure" action that corresponds to setPasswordRequest
. The errors and loading state are managed in the Redux store.
But in order to set a form's errors and loading state with Formik, you need to use setErrors
and setSubmitting
from the Formik.onSubmit
prop.
I could pass setErrors
and setSubmitting
to setPasswordRequest
and call them in the saga, but that seems messy. Anyone know the best way to approach this?
Here is what work weel for me. Quite similar to @t-nunes example, but without changing statuses until next submission.
Examples for both versions
@ivankoleda Thank you for your examples, I was able to implement Formik with server-side validation. Just one minor thing tho, a little bit of inconsistency in the dirty
variable.
At times with the error, the dirty
variable will be set to true
so that the user can reset the form, but other times it will not set it to true
so the user has to reset the form one by one.
How do I fix this issue? Is there a workaround?
Thank you in advance.
@danielbyun dirty
variable and Reset button hasn't been removed after forking default formik codesandbox they are redundant here. However it has no impact on validation. If I missed something please describe some real life case or create sandbox where described behavior creates some inconsistencies.
Is there any way to use this with formik-material-ui
?
@chetanyakan
you can set status in submit handler like this
setStatus({ error: error._error, apiErrors: error.errors });
then use @ivankoleda code for error
const getError = (name, { touched, errors, status }) => {
const fieldTouched = getIn(touched, name);
const backendError = getIn(status, ["apiErrors", name]);
const clientError = getIn(errors, name);
if (clientError && fieldTouched) {
return clientError;
}
if (backendError && !fieldTouched) {
return backendError;
}
return undefined;
};
then you have two options:
const emailError = getError("email", {
touched,
errors,
status,
});
and pass the email error to the component like this:
<Field
type="email"
name="email"
component={TextField}
label={"Email"}
error={!!emailError}
helperText={emailError}
/>
Here is a simple form with errors example:
When I submit form, I receive backend errors:
{ surname: 'Exists' }
. So, I set field errors and now they are:Now, if I change or blur the value of the
name
field, the whole form validation will be triggered and backend error will be erased forsurname
field.How is it possible to erase field's backend error only if the value of that field was changed?
I can think of possible solution with using form's
status
to save backend errors, checking the field's previous/new value, but it feels like a really big hack.