import { useRef, useMemo, FocusEvent, ChangeEvent, useCallback } from "react"; import { useDidMount, useDidUpdate, useWillMount } from "@reins/hooks"; import { UseFieldOptions, UseFieldOutput } from "./use-field.types"; import { FieldValidation, FormAdapter } from "utils"; export const createUseField = ( useFormContext: (componentName: string) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any form: FormAdapter; }, ) => // eslint-disable-next-line @typescript-eslint/no-explicit-any
, Value>( name: string, options?: UseFieldOptions, ): UseFieldOutput => { const { attachFieldElement = true, onBlur, onChange, ...validation } = options || {}; const { form } = useFormContext(`${name}-useField`); // any use on purpose // eslint-disable-next-line @typescript-eslint/no-explicit-any const ref = useRef(null); const fieldPaths = useMemo(() => name.split("."), [name]); const [{ value, error, isTouched, isDirty, isValidating }] = form.store.useStore({ dependencyPaths: [ ["values", ...fieldPaths], ["errors", name], ["validations", name], ["touched", name], ["dirty", name], ["submitCount"], ], selector: (state) => { const touched = !!state.submitCount || state.touched[name] || false; const errorMessage = state.errors[name]?.message ? String(state.errors[name]?.message) : ""; return { value: fieldPaths.reduce((acc, curr) => acc[curr], state.values), error: touched ? errorMessage : undefined, isDirty: state.dirty[name] || false, isTouched: touched, isValidating: state.validations[name] || false, }; }, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any const handleChange = useCallback((newValue: any, event: ChangeEvent) => { form.setFieldValue(name, newValue); onChange?.(newValue, event); }, []); // eslint-disable-next-line @typescript-eslint/no-explicit-any const handleBlur = useCallback((event: FocusEvent) => { form.setFieldTouched(name, true); onBlur?.(value, event); }, []); useWillMount(() => form.registerField(name)); useDidMount(() => { if (!attachFieldElement) return; if (!ref.current) { if (process.env.NODE_ENV === "development") { console.warn( "It looks like you doesn't have 'ref' attached on mount for 'useField' hook. " + "Perhaps you render your field conditionally which is not allowed or forgot to attach it to field. " + "Please ensure that you render your attached ref without any conditions and it's accessible on mount. " + "If you don't care about this feature, pass 'attachFieldElement: false' option for 'useField' hook.", ); } return; } return ref.current ? form.attachFieldElement(name, ref.current) : undefined; }); useDidUpdate( () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any return form.registerFieldValidation(name, validation as FieldValidation, any>); }, [name, validation], true, ); return { field: { ref, value, onChange: handleChange, onBlur: handleBlur, }, error, isDirty, isTouched, isValidating, }; };