UNPKG

remix-validated-form

Version:

Form component and utils for easy form validation in remix

65 lines (64 loc) 2.8 kB
import { atom } from "jotai"; import { atomFamily, selectAtom } from "jotai/utils"; import omit from "lodash/omit"; import { fieldAtomFamily, formAtomFamily, } from "./state/atomUtils"; export const ATOM_SCOPE = Symbol("remix-validated-form-scope"); export const isHydratedAtom = formAtomFamily(false); export const isSubmittingAtom = formAtomFamily(false); export const hasBeenSubmittedAtom = formAtomFamily(false); export const fieldErrorsAtom = formAtomFamily({}); export const touchedFieldsAtom = formAtomFamily({}); export const formPropsAtom = formAtomFamily({ validateField: () => Promise.resolve(null), registerReceiveFocus: () => () => { }, defaultValues: {}, }); export const formElementAtom = formAtomFamily(null); //// Everything below is derived from the above export const cleanupFormState = (formId) => { [ isHydratedAtom, isSubmittingAtom, hasBeenSubmittedAtom, fieldErrorsAtom, touchedFieldsAtom, formPropsAtom, ].forEach((formAtom) => formAtom.remove(formId)); }; export const isValidAtom = atomFamily((formId) => atom((get) => Object.keys(get(fieldErrorsAtom(formId))).length === 0)); export const startSubmitAtom = atomFamily((formId) => atom(null, (_get, set) => { set(isSubmittingAtom(formId), true); set(hasBeenSubmittedAtom(formId), true); })); export const endSubmitAtom = atomFamily((formId) => atom(null, (_get, set) => { set(isSubmittingAtom(formId), false); })); export const setTouchedAtom = atomFamily((formId) => atom(null, (get, set, { field, touched }) => { const prev = get(touchedFieldsAtom(formId)); if (prev[field] !== touched) { set(touchedFieldsAtom(formId), { ...prev, [field]: touched, }); } })); export const setFieldErrorAtom = atomFamily((formId) => atom(null, (get, set, { field, error }) => { const prev = get(fieldErrorsAtom(formId)); if (error === undefined && field in prev) { set(fieldErrorsAtom(formId), omit(prev, field)); } if (error !== undefined && prev[field] !== error) { set(fieldErrorsAtom(formId), { ...get(fieldErrorsAtom(formId)), [field]: error, }); } })); //// Field specific export const fieldTouchedAtom = fieldAtomFamily(({ formId, field }) => atom((get) => get(touchedFieldsAtom(formId))[field], (_get, set, touched) => { set(setTouchedAtom(formId), { field, touched }); })); export const fieldErrorAtom = fieldAtomFamily(({ formId, field }) => atom((get) => get(fieldErrorsAtom(formId))[field], (_get, set, error) => { set(setFieldErrorAtom(formId), { field, error }); })); export const fieldDefaultValueAtom = fieldAtomFamily(({ formId, field }) => selectAtom(formPropsAtom(formId), (state) => state.defaultValues[field]));