-
-
Save manzoorwanijk/5993a520f2ac7890c3b46f70f6818e0a to your computer and use it in GitHub Desktop.
import * as yup from 'yup'; | |
import { setIn } from 'final-form'; | |
const validationSchema = yup.object({ | |
email: yup.string().email(), | |
shipping: yup.object({ | |
name: yup.string(), | |
phone: yup.object({ | |
code: yup.string().matches(/^\+\d+$/i), | |
number: yup.number().max(10), | |
}), | |
address: yup.string(), | |
zip: yup.string(), | |
}), | |
billing: yup.object({ | |
name: yup.string(), | |
address: yup.string(), | |
zip: yup.string(), | |
}), | |
items: yup.array().of( | |
yup.object({ | |
id: yup.number(), | |
price: yup.number(), | |
quantity: yup.number(), | |
}) | |
), | |
}); | |
// To be passed to React Final Form | |
const validateFormValues = (schema) => async (values) => { | |
if (typeof schema === 'function') { | |
schema = schema(); | |
} | |
try { | |
await schema.validate(values, { abortEarly: false }); | |
} catch (err) { | |
const errors = err.inner.reduce((formError, innerError) => { | |
return setIn(formError, innerError.path, innerError.message); | |
}, {}); | |
return errors; | |
} | |
}; | |
const validate = validateFormValues(validationSchema); | |
const MyForm = () => ( | |
<Form // from react-final-form | |
onSubmit={onSubmit} | |
validate={validate} | |
/> | |
); |
If I may suggest a slight change to your function:
const validate = schema => async values => {
if (typeof schema === 'function')
schema = schema();
try {
await schema.validate(values, { abortEarly: false });
} catch (e) {
return e.inner.reduce((errors, error) => {
return setIn(errors, error.path, error.message);
}, {});
}
};
This will allow a simpler use in the form itself:
<Form
onSubmit={onSubmit}
validate={validate(schema)}
render={({ handleSubmit, form, submitting, pristine, values }) => (
@miooim This causes my whole form to rerender on input change even when im using the subscription prop of the Form
component, do you know how to prevent this rerender? thanks!
@Maadtin that's because the function call returns a new object everytime thus causing a change in props which triggers re-render. So it's better to use it the way mentioned in the original gist.
If you still want to use it like that, then save the value returned by function call to a variable outside the component and use that variable in props.
I have updated the gist.
I created a hook based off of this gist.
const validationSchema = yup.object().shape({
email: yup.string().email().required(),
password: yup.string().min(5).required()
});
const initialValues = Object.freeze({
email: '',
password: ''
});
function Login() {
const validate = useValidationSchema(validationSchema); // <-- Create RFF validation function
const handleSubmit = useCallback(
({ email, password }) => alert(`Signing in with ${email} // ${password}`),
[]
);
return (
<Form validate={validate} initialValues={initialValues} onSubmit={handleSubmit}>
{/* ... */}
</Form>
);
}
export default Login;
Feel free to comment or borrow ideas.
Nicely done! It should be converted to yup wrapper for react :)
@nfantone, thank you.
But in my opinion, a hook is not needed for this and it can be a simple function which can be called outside the component because the schema may not change per component.
@manzoorwanijk The module I shared exports both a "simple function" and a hook to use inside components. You can chose to use the one that suits your needs. The hook is just a trivial wrapper that memoizes the function - so both approaches work pretty much the same for the basic scenario. If your components are uncomplicated enough, you're right: you could create the validate
function outside the component closure once and re-use it.
That's not always possible, however. A hook may be needed if:
- you need to support i18n on message errors.
- you need/want to defer the creation of your
yup
schema to component mount. - your
yup
schema depends on some data from your component state and needs to be (re-)generated off of it (e.g.: wizards, dynamic forms).
with typescript resolver
import { setIn } from "final-form";
import { AnySchema, ValidationError } from "yup";
export function yupResolver(schema: AnySchema) {
return async (value: object) => {
try {
await schema.validate(value, { abortEarly: false });
} catch (error) {
if (error instanceof ValidationError) {
return error.inner.reduce((errors, error: ValidationError) => {
const path = error.path ?? "global";
return setIn(errors, path, error.message);
}, {});
}
throw error;
}
};
}
<Form
onSubmit={(fields) => console.log(fields)}
validate={yupResolver(SupplierSchema)}...
with typescript resolver
import { setIn } from "final-form"; import { AnySchema, ValidationError } from "yup"; export function yupResolver(schema: AnySchema) { return async (value: object) => { try { await schema.validate(value, { abortEarly: false }); } catch (error) { if (error instanceof ValidationError) { return error.inner.reduce((errors, error: ValidationError) => { const path = error.path ?? "global"; return setIn(errors, path, error.message); }, {}); } throw error; } }; }<Form onSubmit={(fields) => console.log(fields)} validate={yupResolver(SupplierSchema)}...
thank you
Worked perfectly. I wish this was in the final-form docs.