Skip to content

Commit c6f644f

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 e474d2b commit c6f644f

File tree

1 file changed

+12
-1
lines changed

1 file changed

+12
-1
lines changed

src/useFormState.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,22 @@ function useFormState<FormValues = Record<string, any>>({
2020
return form.getState();
2121
});
2222

23+
// Track whether this is the first subscription callback (for initial state)
24+
const firstCallRef = React.useRef(true);
25+
2326
React.useEffect(() => {
2427
// Subscribe to form state changes after initial render
2528
const unsubscribe = form.subscribe((newState) => {
2629
setState((prevState) => {
27-
if (!shallowEqual(newState, prevState)) {
30+
// Always call onChange on first subscription (initial state), even if equal
31+
// This fixes #809 - ensures onChange is called after render, not during
32+
const isFirstCall = firstCallRef.current;
33+
if (isFirstCall) {
34+
firstCallRef.current = false;
35+
}
36+
37+
const stateChanged = !shallowEqual(newState, prevState);
38+
if (stateChanged || isFirstCall) {
2839
if (onChangeRef.current) {
2940
onChangeRef.current(newState);
3041
}

0 commit comments

Comments
 (0)