import type { ControlState, FormContextType, FormControlName, } from 'domains/forms/forms.types' import { getControlTouchedByName, getControlValueByName, getFormById, } from 'domains/forms/selectors' import { deregisterControl, registerControl } from 'domains/forms/slice' import { useAppDispatch, type RootState } from 'domains/store' import { useCallback, useContext, useEffect, useLayoutEffect, useMemo, } from 'preact/hooks' import { useSelector } from 'react-redux' import FormContext from './context' import { validate } from './utils' export function useFormContext(): FormContextType { return useContext(FormContext) } export function useForm() { const { handleSubmit, isSubmitted, isValid } = useFormContext() return { handleSubmit, isSubmitted, isValid, } } export function useValidations(values, validationSchema) { const errors = useMemo( () => validate(values, validationSchema), [values, validationSchema], ) return { isValid: Object.keys(errors).length === 0, errors, } } export function useFormControl( name: T, ): [ { name: T onInput: (_e: any) => void onBlur: () => void value: T extends 'textMessageEntry' ? string : FileList }, ControlState, ] { const dispatch = useAppDispatch() const { formId, updateControlValue, updateControlTouched, errors } = useFormContext() const form = useSelector((state: RootState) => getFormById(state, { formId })) const isRegistered = !!form const value = useSelector((state: RootState) => { return getControlValueByName(state, { formId, name }) }) const touched = useSelector((state: RootState) => getControlTouchedByName(state, { formId, name }), ) const error = errors?.[name] const isValid = !error useEffect(() => { // Make sure the form is registered // Since child useEffect runs before FormProvider useEffect if (isRegistered) { dispatch(registerControl({ formId, name })) } }, [isRegistered, formId, name, dispatch]) useLayoutEffect(() => { return () => { dispatch(deregisterControl({ formId, name })) } }, [isRegistered, formId, name, dispatch]) // preact uses onInput instead of onChange const onInput = useCallback( (e) => updateControlValue(name, e.target.value), [name, updateControlValue], ) const onBlur = useCallback(() => { updateControlTouched(name, true) }, [updateControlTouched, name]) const field = useMemo( () => ({ name, onInput, onBlur, value, }), [name, onInput, onBlur, value], ) const meta = useMemo( () => ({ isValid, error, touched, }), [isValid, error, touched], ) return [field, meta] }