Skip to content

Instantly share code, notes, and snippets.

@nfantone
Last active May 29, 2024 01:55
Show Gist options
  • Save nfantone/9ab600760db8774ab4873cb1a3a22f26 to your computer and use it in GitHub Desktop.
Save nfantone/9ab600760db8774ab4873cb1a3a22f26 to your computer and use it in GitHub Desktop.
Use `yup` with `react-final-form`
import { setIn } from 'final-form';
import { useMemo } from 'react';
/**
* Sets the `innerError.message` in an `errors` object at the key
* defined by `innerError.path`.
* @param {Object} errors The object to set the error in.
* @param {{ path: string, message: string }} innerError A `yup` field error.
* @returns {Object} The result of setting the new error message onto `errors`.
*/
const setInError = (errors, innerError) => {
return setIn(errors, innerError.path, innerError.message);
};
/**
* Empty object map with no prototype. Used as default
* value for reducing the `err.inner` array of errors
* from a `yup~ValidationError`.
* @type {Object}
*/
const emptyObj = Object.create(null);
/**
* Takes a `yup` validation schema and returns a function that expects
* a map of values to validate. If the validation passes, the function resolves to `undefined`
* (signalling that the values are valid). If the validation doesn't pass, it resolves
* to a map of invalid field names to errors.
* @param {import('yup').ObjectSchema} schema `yup` schema definition.
* @returns {(values: Object) => Promise<?Object>} An async function that expects some `values`
* and resolves to either `undefined` or a map of field names to error messages.
*/
export const makeValidate = schema => {
return async function validate(values) {
try {
await schema.validate(values, { abortEarly: false });
} catch (err) {
return err.inner.reduce(setInError, emptyObj);
}
};
};
function useValidationSchema(schema) {
const validate = useMemo(() => makeValidate(schema), [schema]);
return validate;
}
export default useValidationSchema;
@hoangbits
Copy link

hoangbits commented Jan 17, 2021

for those who getting: TypeError: can't define property "x": "obj" is not extensible
You can consider not using Object.freeze and set
const validateOptions = { abortEarly: false };
only.

@nfantone
Copy link
Author

Yeah - this actually used to work with previous versions of yup. Now, they seem to be mutating the options object you pass in. So, instead of removing the Object.freeze call, we should pass a new object on every call. I'll amend the gist to reflect this. Thank you @legiahoang!

@litewarp
Copy link

litewarp commented Feb 12, 2022

Converted it to TS for anyone stumbling upon this:

// nfantone/use-validation-schema.js
// https://gist.github.com/nfantone/9ab600760db8774ab4873cb1a3a22f26
import { setIn } from 'final-form'
import { useMemo } from 'react'
import { AnyObjectSchema, ValidationError } from 'yup'

/**
 * Sets the `innerError.message` in an `errors` object at the key
 * defined by `innerError.path`.
 */
const setInError = (errors: Record<string, string>, innerError: ValidationError) => {
  return setIn(errors, innerError.path ?? '', innerError.message)
}

/**
 * Empty object map with no prototype. Used as default
 * value for reducing the `err.inner` array of errors
 * from a `yup~ValidationError`.
 */
const emptyObj: {} = Object.create(null)

/**
 * Takes a `yup` validation schema and returns a function that expects
 * a map of values to validate. If the validation passes, the function resolves to `undefined`
 * (signalling that the values are valid). If the validation doesn't pass, it resolves
 * to a map of invalid field names to errors.
 */

export function makeValidate<TValue = Record<string, unknown>>(
  schema: AnyObjectSchema
): (val: TValue) => Promise<undefined | Record<string, string>> {
  return async function validate(values: TValue) {
    try {
      await schema.validate(values, { abortEarly: false })
    } catch (err) {
      return (err as ValidationError).inner.reduce(setInError, emptyObj)
    }
  }
}

export function useValidationSchema<TValue = Record<string, unknown>>(
  schema: AnyObjectSchema
) {
  const validate = useMemo(() => makeValidate(schema), [schema])
  return validate
}

@Ksondzyk
Copy link

Ksondzyk commented Feb 18, 2023

const setInError = (errors: ValidationError, innerError: ValidationError): ValidationError =>
	<ValidationError>setIn(errors, innerError.path ?? '', innerError.message);

const emptyObj: ValidationError = Object.create(null);

export const makeValidate = (schema: ObjectSchema<AnyObject>) => {
	return async function validate(values: InferType<typeof schema>) {
		try {
			await schema.validate(values, { abortEarly: false });
		} catch (errors: unknown) {
			if (errors instanceof ValidationError) {
				return errors.inner.reduce(setInError, emptyObj);
			}
		}
	};
};

export const useValidationSchema = (schema: ObjectSchema<AnyObject>) => {
	return useMemo(() => makeValidate(schema), [schema]);
};

@mrasadatik
Copy link

TypeScript Users check out this gist.

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