UNPKG

@formulier/react

Version:

Simple, performant form library for React

65 lines (64 loc) 3.02 kB
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { arrayUtils, stateUtils } from '@formulier/core'; import { invariant, useEvent } from './utils.js'; import { useFormSelector } from './form.js'; const callCallback = (cb) => void cb(); function useFormField(form, options) { const id = React.useId(); const rerender = React.useReducer(x => x + 1, 0)[1]; const { name, validate, valueOptions, flushSyncOnChange = false, flushSyncOnBlur = false } = options; const value = useFormFieldValue(form, name, valueOptions); const error = useFormSelector(form, state => state.errors[name]) || null; const touched = useFormSelector(form, state => state.touched[name]) ?? false; const hasSubmitted = useFormSelector(form, state => state.submitCount > 0); React.useEffect(() => { const unregisterField = form.registerField(name, validate); const removeInstance = form.addInstance(name, id); return () => { removeInstance(); queueMicrotask(() => { ReactDOM.flushSync(rerender); if (!form.hasInstance(name)) unregisterField(); }); }; }, [form, name, id, validate, rerender]); const onChange = useEvent((value) => { const maybeFlushSync = flushSyncOnChange ? ReactDOM.flushSync : callCallback; maybeFlushSync(() => { form.setFieldValue(name, value); if (touched || hasSubmitted) form.validateField(name); }); }); const onBlur = useEvent(() => { const maybeFlushSync = flushSyncOnBlur ? ReactDOM.flushSync : callCallback; maybeFlushSync(() => { form.validateField(name); form.touchField(name); }); }); const field = { id, name, value, onChange, onBlur }; const meta = { error, touched }; return [field, meta]; } function useFormFieldArray(form, name, options) { const { valueOptions } = options || {}; const items = useFormFieldValue(form, name, valueOptions); invariant(Array.isArray(items), `Field "${name}" is not an array.`); const update = (value) => void form.setFieldValue(name, value); const arrayMethods = { push: useEvent(item => update(arrayUtils.push(items, item))), insert: useEvent((item, index) => update(arrayUtils.insert(items, item, index))), remove: useEvent(index => update(arrayUtils.remove(items, index))), move: useEvent((fromIndex, toIndex) => update(arrayUtils.move(items, fromIndex, toIndex))), swap: useEvent((fromIndex, toIndex) => update(arrayUtils.swap(items, fromIndex, toIndex))), }; return [items, arrayMethods]; } function useFormFieldValue(form, name, options) { const { fallback, equalityFn = stateUtils.isEqual } = options || {}; return useFormSelector(form, state => stateUtils.getPath(state.values, name, fallback), equalityFn); } export { useFormField, useFormFieldArray, useFormFieldValue };