@formulier/react
Version:
Simple, performant form library for React
65 lines (64 loc) • 3.02 kB
JavaScript
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 };