Skip to content

Instantly share code, notes, and snippets.

@maradondt
Created October 10, 2024 12:14
Show Gist options
  • Save maradondt/e690b8d983ceaa987f90746aa3a1b012 to your computer and use it in GitHub Desktop.
Save maradondt/e690b8d983ceaa987f90746aa3a1b012 to your computer and use it in GitHub Desktop.
yup custom validation hook
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { set } from 'lodash';
import { useMemo } from 'react';
import { Schema, ValidationError } from 'yup';
/**
* 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 = <T extends Record<string, any>>(
errors: T,
innerError: ValidationError
): { [key in keyof T]: string } => {
if (!innerError.path) return errors;
return set(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);
// type Primitive = number | string | boolean;
type ObjectErrors<T> = {
[key in keyof T]?: T[key] extends object ? ObjectErrors<T[key]> : string | null;
};
export type ValidationResult<T> = (T extends object ? ObjectErrors<T> : unknown) | 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 `null`
* (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 `null` or a map of field names to error messages.
*/
export const makeValidate = (schema?: Schema) => {
if (!schema) return <T extends object = object>(_obj: T) => Promise.resolve(null);
return async function validate<T extends object = object>(values: T): Promise<ValidationResult<T>> {
try {
const resp = await schema.validate(values, { abortEarly: false });
return null;
} catch (err) {
const error = err as ValidationError;
return error.inner.reduce((acc, err) => setInError<T>(acc, err), emptyObj);
}
};
};
export function useValidationSchema(schema?: Schema) {
const validate = useMemo(() => makeValidate(schema), [schema]);
return validate;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment