import type {DependencyList, Dispatch} from 'react'; import {useCallback, useEffect, useState} from 'react'; import {useSyncedRef} from '../useSyncedRef/index.js'; import type {InitialState, NextState} from '../util/resolve-hook-state.js'; export type ValidityState = { isValid: boolean | undefined; } & Record; export type ValidatorImmediate = () => V; export type ValidatorDeferred = (done: Dispatch>) => any; export type Validator = ValidatorImmediate | ValidatorDeferred; export type UseValidatorReturn = [V, () => void]; /** * Performs validation when any of provided dependencies has changed. * * @param validator Function that performs validation. * @param deps Dependencies list that passed straight to underlying `useEffect`. * @param initialValidity Initial validity state. */ export function useValidator( validator: Validator, deps: DependencyList, // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion initialValidity: InitialState = {isValid: undefined} as V, ): UseValidatorReturn { const [validity, setValidity] = useState(initialValidity); const validatorRef = useSyncedRef(() => { if (validator.length > 0) { validator(setValidity); } else { // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion setValidity((validator as ValidatorImmediate)()); } }); useEffect(() => { validatorRef.current(); // eslint-disable-next-line react-hooks/exhaustive-deps }, deps); return [ validity, useCallback(() => { validatorRef.current(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []), ]; }