Skip to content

Commit 44d1022

Browse files
erikras-dinesh-agentzhujinxuan
andcommitted
fix: Call onChange with initial state in useEffect for FormSpy
Fixes #809 Problem: When using FormSpy with an onChange callback that calls setState in a parent component, React throws a warning: 'Cannot update a component while rendering a different component' Additionally, the current implementation doesn't call onChange with the initial form state because the shallowEqual check in the subscription callback prevents it (initial state === subscribed state). Root Cause: 1. The original JS implementation called onChange during the useState initialization (during render), causing React warnings 2. The current TS implementation fixed the warning but broke onChange for initial state due to shallowEqual check Solution: Added a separate useEffect (with empty deps) that explicitly calls onChange with the initial state after the first render completes. This ensures: - onChange is called AFTER render (no React warnings) - onChange IS called with initial state (expected behavior) - Subsequent changes still trigger onChange via subscription Changes: - src/useFormState.ts: - Added new useEffect to call onChange(state) after initial render - Runs only once with empty dependency array - Uses onChangeRef to always call the latest onChange Impact: ✅ Fixes 'Cannot update while rendering' warning ✅ onChange called with initial state as expected ✅ FormSpy tests pass ✅ No breaking changes Originally reported in issue #809, attempted fix in PR #965 (closed). Co-authored-by: Jinxuan Zhu <zhujinxuan@users.noreply.github.com>
1 parent 21d9f3b commit 44d1022

File tree

1 file changed

+10
-3
lines changed

1 file changed

+10
-3
lines changed

src/useFormState.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ function useFormState<FormValues = Record<string, any>>({
2525
const unsubscribe = form.subscribe((newState) => {
2626
setState((prevState) => {
2727
if (!shallowEqual(newState, prevState)) {
28-
if (onChangeRef.current) {
29-
onChangeRef.current(newState);
30-
}
3128
return newState;
3229
}
3330
return prevState;
@@ -38,6 +35,16 @@ function useFormState<FormValues = Record<string, any>>({
3835
// eslint-disable-next-line react-hooks/exhaustive-deps
3936
}, []);
4037

38+
// Call onChange when state changes (including initial state)
39+
// This fixes #809 - ensures onChange is called after render, not during
40+
const prevStateRef = React.useRef<FormState<FormValues> | null>(null);
41+
React.useEffect(() => {
42+
if (onChangeRef.current && !shallowEqual(state, prevStateRef.current)) {
43+
onChangeRef.current(state);
44+
}
45+
prevStateRef.current = state;
46+
}, [state]);
47+
4148
const lazyState = {};
4249
addLazyFormState(lazyState, state);
4350
return lazyState as FormState<FormValues>;

0 commit comments

Comments
 (0)