Skip to content

Instantly share code, notes, and snippets.

@manzoorwanijk
Last active August 22, 2024 11:44
Show Gist options
  • Save manzoorwanijk/5993a520f2ac7890c3b46f70f6818e0a to your computer and use it in GitHub Desktop.
Save manzoorwanijk/5993a520f2ac7890c3b46f70f6818e0a to your computer and use it in GitHub Desktop.
How to properly use yup validation schema with (React) Final Form?
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}
/>
);
@AdamCottrill
Copy link

Worked perfectly. I wish this was in the final-form docs.

@miooim
Copy link

miooim commented May 30, 2020

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 }) => (

@Maadtin
Copy link

Maadtin commented Jul 1, 2020

@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!

@manzoorwanijk
Copy link
Author

@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.

@manzoorwanijk
Copy link
Author

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.

@manzoorwanijk
Copy link
Author

I have updated the gist.

@nfantone
Copy link

nfantone commented Aug 27, 2020

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.

@miooim
Copy link

miooim commented Aug 27, 2020

Nicely done! It should be converted to yup wrapper for react :)

@manzoorwanijk
Copy link
Author

@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.

@nfantone
Copy link

nfantone commented Aug 28, 2020

@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).

@franklinST-05
Copy link

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)}...

@shifoc
Copy link

shifoc commented Aug 22, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment