pocket-hook-form
Version:
pocket-store base hook form
103 lines (94 loc) • 2.65 kB
text/typescript
// hooks/useController.ts
import {useCallback, useEffect, useMemo, useRef} from 'react';
import type {
FormStoreControl,
FormValue,
FieldRef,
UseControllerReturn,
FormState,
FieldRules,
} from '../models/type';
import getEventValue from '../utils/getEventValue';
import convertToArray from '../utils/convertToArray';
import {Store, useStore} from 'pocket-state';
export function useController<T extends FormValue, K extends keyof T>(
control: FormStoreControl<T>,
name: K,
opts?: {
next?: keyof T;
rules?: FieldRules<T[K], T>;
},
): UseControllerReturn<T, K> {
const store = (control as any)._store as Store<FormState<T>>;
const {value, errors, touched, isDirty} = useStore(store, s => ({
value: s.values[name],
errors: s.errors?.[name],
touched: !!s.touched[name],
isDirty: !!s.dirty[name],
}));
const ref = useRef<FieldRef>(null);
useEffect(() => {
if (!opts?.rules) return;
const current = (control.getStoreOptions().rules ?? {}) as Partial<{
[P in keyof T]: FieldRules<T[P], T>;
}>;
const prevField = current[name] as FieldRules<T[K], T> | undefined;
const mergedField: FieldRules<T[K], T> = {
...(prevField ?? {}),
...(opts.rules ?? {}),
};
control.updateOptions({
rules: {
...current,
[name]: mergedField,
} as Partial<{[P in keyof T]: FieldRules<T[P], T>}>,
});
return () => {
const latestRules = (control.getStoreOptions().rules ?? {}) as Partial<{
[P in keyof T]: FieldRules<T[P], T>;
}>;
if (latestRules[name]) {
delete latestRules[name];
control.updateOptions({
rules: latestRules,
});
}
};
}, [control, name, opts?.rules]);
useEffect(() => {
control.addRef(name, ref.current);
return () => control.removeRef(name);
}, [control, name]);
const onChange = useCallback(
(e: any) => {
const v = getEventValue(e);
control.setValue(name, v);
},
[control, name],
);
const onBlur = useCallback(() => {
const {mode} = control.getStoreOptions();
control.setTouched?.(name, true);
if (mode === 'onBlur') {
(control as any).validateField?.(name);
}
}, [control, name]);
const onSubmitEditing = useCallback(() => {
if (opts?.next) control.focus(opts.next);
}, [control, opts?.next]);
return useMemo(
() => ({
value,
onChange,
onBlur,
ref,
onSubmitEditing,
fieldState: {
touched,
isDirty,
errors: errors && convertToArray(errors),
},
}),
[value, ref, touched, isDirty, errors],
);
}