Skip to content

Commit 8ac5f4e

Browse files
eps1lonalexdln
andauthored
Fix form status reset when component state is updated (#34075)
Co-authored-by: Vordgi <[email protected]>
1 parent eb89912 commit 8ac5f4e

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed

packages/react-dom/src/__tests__/ReactDOMForm-test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2281,4 +2281,64 @@ describe('ReactDOMForm', () => {
22812281
await submit(formRef.current);
22822282
assertLog(['stringified action']);
22832283
});
2284+
2285+
it('form actions should retain status when nested state changes', async () => {
2286+
const formRef = React.createRef();
2287+
2288+
let rerenderUnrelatedStatus;
2289+
function UnrelatedStatus() {
2290+
const {pending} = useFormStatus();
2291+
const [counter, setCounter] = useState(0);
2292+
rerenderUnrelatedStatus = () => setCounter(n => n + 1);
2293+
Scheduler.log(`[unrelated form] pending: ${pending}, state: ${counter}`);
2294+
}
2295+
2296+
let rerenderTargetStatus;
2297+
function TargetStatus() {
2298+
const {pending} = useFormStatus();
2299+
const [counter, setCounter] = useState(0);
2300+
Scheduler.log(`[target form] pending: ${pending}, state: ${counter}`);
2301+
rerenderTargetStatus = () => setCounter(n => n + 1);
2302+
}
2303+
2304+
function App() {
2305+
async function action() {
2306+
return new Promise(resolve => {
2307+
// never resolves
2308+
});
2309+
}
2310+
2311+
return (
2312+
<>
2313+
<form action={action} ref={formRef}>
2314+
<input type="submit" />
2315+
<TargetStatus />
2316+
</form>
2317+
<form>
2318+
<UnrelatedStatus />
2319+
</form>
2320+
</>
2321+
);
2322+
}
2323+
2324+
const root = ReactDOMClient.createRoot(container);
2325+
await act(() => root.render(<App />));
2326+
2327+
assertLog([
2328+
'[target form] pending: false, state: 0',
2329+
'[unrelated form] pending: false, state: 0',
2330+
]);
2331+
2332+
await submit(formRef.current);
2333+
2334+
assertLog(['[target form] pending: true, state: 0']);
2335+
2336+
await act(() => rerenderTargetStatus());
2337+
2338+
assertLog(['[target form] pending: true, state: 1']);
2339+
2340+
await act(() => rerenderUnrelatedStatus());
2341+
2342+
assertLog(['[unrelated form] pending: false, state: 1']);
2343+
});
22842344
});

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1974,7 +1974,7 @@ function updateHostComponent(
19741974
// If the transition state changed, propagate the change to all the
19751975
// descendents. We use Context as an implementation detail for this.
19761976
//
1977-
// This is intentionally set here instead of pushHostContext because
1977+
// We need to update it here because
19781978
// pushHostContext gets called before we process the state hook, to avoid
19791979
// a state mismatch in the event that something suspends.
19801980
//

packages/react-reconciler/src/ReactFiberHostContext.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import type {Fiber} from './ReactInternalTypes';
1111
import type {StackCursor} from './ReactFiberStack';
12-
import type {Container, HostContext} from './ReactFiberConfig';
12+
import type {
13+
Container,
14+
HostContext,
15+
TransitionStatus,
16+
} from './ReactFiberConfig';
1317
import type {Hook} from './ReactFiberHooks';
1418

1519
import {
@@ -92,6 +96,21 @@ function getHostContext(): HostContext {
9296
function pushHostContext(fiber: Fiber): void {
9397
const stateHook: Hook | null = fiber.memoizedState;
9498
if (stateHook !== null) {
99+
// Propagate the current state to all the descendents.
100+
// We use Context as an implementation detail for this.
101+
//
102+
// NOTE: This assumes that there cannot be nested transition providers,
103+
// because the only renderer that implements this feature is React DOM,
104+
// and forms cannot be nested. If we did support nested providers, then
105+
// we would need to push a context value even for host fibers that
106+
// haven't been upgraded yet.
107+
const transitionStatus: TransitionStatus = stateHook.memoizedState;
108+
if (isPrimaryRenderer) {
109+
HostTransitionContext._currentValue = transitionStatus;
110+
} else {
111+
HostTransitionContext._currentValue2 = transitionStatus;
112+
}
113+
95114
// Only provide context if this fiber has been upgraded by a host
96115
// transition. We use the same optimization for regular host context below.
97116
push(hostTransitionProviderCursor, fiber, fiber);

0 commit comments

Comments
 (0)