remix-validated-form
Version:
Form component and utils for easy form validation in remix
65 lines (64 loc) • 2.8 kB
JavaScript
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]));