This repository has been archived by the owner on Jul 27, 2022. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
useFormState.ts
102 lines (91 loc) 路 2.89 KB
/
useFormState.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { useReducer, useRef, useCallback } from "react";
import isEqual from "fast-deep-equal";
import {
Debug,
FormState,
FormStateReturn,
ResetStateRef,
SetStateRef,
UsedRef,
} from "./types";
import useLatest from "./useLatest";
import { get, isEmptyObject, set } from "./utils";
export default <V>(
initialValues: V,
onChange?: Debug<V>
): FormStateReturn<V> => {
const [, forceUpdate] = useReducer((c) => c + 1, 0);
const initialStateRef = useRef<FormState<V>>({
values: initialValues,
touched: {},
errors: {},
isDirty: false,
dirtyFields: {},
isValidating: false,
isValid: true,
isSubmitting: false,
isSubmitted: false,
submitCount: 0,
});
const stateRef = useRef(initialStateRef.current);
const usedStateRef = useRef<UsedRef>({});
const onChangeRef = useLatest(onChange);
const setStateRef = useCallback<SetStateRef>(
(path, value) => {
const key = path.split(".")[0];
const shouldUpdate =
key === "values" || !isEqual(get(stateRef.current, path), value);
if (shouldUpdate) {
const nextState = set(stateRef.current, path, value, true);
const {
values,
errors,
isDirty: prevIsDirty,
isValid: prevIsValid,
} = nextState;
let { submitCount: prevSubmitCount } = nextState;
const isDirty =
key === "values"
? !isEqual(values, initialStateRef.current.values)
: prevIsDirty;
const isValid = key === "errors" ? isEmptyObject(errors) : prevIsValid;
const submitCount =
key === "isSubmitting" && value
? (prevSubmitCount += 1)
: prevSubmitCount;
stateRef.current = { ...nextState, isDirty, isValid, submitCount };
if (onChangeRef.current) onChangeRef.current(stateRef.current);
if (
Object.keys(usedStateRef.current).some(
(key) => path.startsWith(key) || key.startsWith(path)
) ||
(usedStateRef.current.isDirty && isDirty !== prevIsDirty) ||
(usedStateRef.current.isValid && isValid !== prevIsValid)
)
forceUpdate();
}
},
[onChangeRef]
);
const resetStateRef = useCallback<ResetStateRef<V>>(
(values = initialStateRef.current.values, exclude, callback) => {
Object.keys(initialStateRef.current)
.filter((key) => !exclude.includes(key as keyof FormState<V>))
.forEach((key) => {
if (key === "values") {
stateRef.current[key] = values;
callback(values);
} else {
// @ts-expect-error
stateRef.current[key] = initialStateRef.current[key];
}
});
forceUpdate();
},
[]
);
const setUsedStateRef = useCallback((path: string) => {
usedStateRef.current[path] = true;
}, []);
return { stateRef, setStateRef, resetStateRef, setUsedStateRef };
};