UNPKG

@shopify/react-form

Version:

Manage React forms tersely and safely-typed with no magic using React hooks

79 lines (73 loc) 3.18 kB
import { useMemo, useEffect } from 'react'; import isEqual from 'fast-deep-equal'; import { mapObject, normalizeValidation } from '../../utilities.mjs'; import { useDirty } from '../dirty.mjs'; import { useListReducer, reinitializeAction, resetListAction } from './hooks/reducer.mjs'; import { useHandlers } from './hooks/handlers.mjs'; /* * A custom hook for handling the state and validations of fields for a list of objects which can be built upon. (e.g useList and useDynamicList). * @param listOrConfig - A configuration object specifying both the value and validation config. * @param validationDependencies - An array of dependencies to use to decide when to regenerate validators. * @returns A list of dictionaries of `Field` objects representing the state of your input and a dispatcher which can be used for other hooks to build around base list (e.g useList and useDynamicList). * * @remarks * **Reinitialization:** If the `list` property of the field configuration changes between calls to `useBaseList`, * the field will be reset to use it as it's new default value. * * **Imperative methods:** The returned `Field` objects contains a number of methods used to imperatively alter their state. * These should only be used as escape hatches where the existing hooks and components do not make your life easy, * or to build new abstractions in the same vein as `useForm`, `useSubmit` and friends. * */ function useBaseList(listOrConfig, validationDependencies = []) { const list = Array.isArray(listOrConfig) ? listOrConfig : listOrConfig.list; const validates = useMemo(() => { return Array.isArray(listOrConfig) ? {} : listOrConfig.validates || {}; }, [listOrConfig]); const [state, dispatch] = useListReducer(list); useEffect(() => { if (!isEqual(list, state.initial)) { dispatch(reinitializeAction(list)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [list, dispatch]); const validationConfigs = useMemo(() => mapObject(validates, normalizeValidation), // eslint-disable-next-line react-hooks/exhaustive-deps [validates, ...validationDependencies]); function reset() { dispatch(resetListAction()); } function newDefaultValue(newDefaultItems) { dispatch(reinitializeAction(newDefaultItems)); } const handlers = useHandlers(state, dispatch, validationConfigs); const fields = useMemo(() => { return state.list.map((item, index) => { return mapObject(item, (field, key) => { return { ...field, ...handlers[index][key] }; }); }); }, [state.list, handlers]); const listWithoutFieldStates = useMemo(() => { return state.list.map(item => { return mapObject(item, field => field.value); }); }, [state.list]); const isBaseListDirty = useMemo(() => !isEqual(listWithoutFieldStates, state.initial), [listWithoutFieldStates, state.initial]); const fieldsDirty = useDirty({ fields }); return { fields, dispatch, reset, dirty: fieldsDirty || isBaseListDirty, defaultValue: state.initial, value: listWithoutFieldStates, newDefaultValue }; } export { useBaseList };