Last active
May 29, 2024 01:55
-
-
Save nfantone/9ab600760db8774ab4873cb1a3a22f26 to your computer and use it in GitHub Desktop.
Use `yup` with `react-final-form`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
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!
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
}
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]);
};
TypeScript Users check out this gist.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.