@samitouri / QOS-React-1 / commits / c308cb5905

Disable enablePostpone flag in experimental (#31042)

I don't think we're ready to land this yet since we're using it to run other experiments and our tests. I'm opening this PR to indicate intent to disable and to ensure tests in other combinations still work. Such as enableHalt without enablePostpone. I think we'll also need to rewrite some tests that depend on enablePostpone to preserve some coverage. The conclusion after this experiment is that try/catch around these are too likely to block these signals and consider them error. Throwing works for Hooks and `use()` because the lint rule can ensure that they're not wrapped in try/catch. Throwing in arbitrary functions not quite ecosystem compatible. It's also why there's `use()` and not just throwing a Promise. This might also affect the Catch proposal. The "prerender" for SSR that's supporting "Partial Prerendering" is still there. This just disables the `React.postpone()` API for creating the holes.

Sebastian Markbåge committed Nov 4, 2025 at 23:23 UTC c308cb590598b61a7fc1766e15edf454d758d226
7 files changed +1 -75
packages/react-dom/src/__tests__/ReactDOMFizzStatic-test.js
-66
@@ -400,72 +400,6 @@ describe('ReactDOMFizzStatic', () => {
400 );
401 });
402
403 - // @gate enablePostpone
404 - it('does not fatally error when aborting with a postpone during a prerender', async () => {
405 - let postponedValue;
406 - try {
407 - React.unstable_postpone('aborting with postpone');
408 - } catch (e) {
409 - postponedValue = e;
410 - }
411 -
412 - const controller = new AbortController();
413 - const infinitePromise = new Promise(() => {});
414 - function App() {
415 - React.use(infinitePromise);
416 - return <div>aborted</div>;
417 - }
418 -
419 - const errors = [];
420 - const pendingResult = ReactDOMFizzStatic.prerenderToNodeStream(<App />, {
421 - onError: error => {
422 - errors.push(error);
423 - },
424 - signal: controller.signal,
425 - });
426 - pendingResult.catch(() => {});
427 -
428 - await Promise.resolve();
429 - controller.abort(postponedValue);
430 -
431 - const result = await pendingResult;
432 -
433 - await act(async () => {
434 - result.prelude.pipe(writable);
435 - });
436 - expect(getVisibleChildren(container)).toEqual(undefined);
437 - expect(errors).toEqual([]);
438 - });
439 -
440 - // @gate enablePostpone
441 - it('does not fatally error when aborting with a postpone during a prerender from within', async () => {
442 - let postponedValue;
443 - try {
444 - React.unstable_postpone('aborting with postpone');
445 - } catch (e) {
446 - postponedValue = e;
447 - }
448 -
449 - const controller = new AbortController();
450 - function App() {
451 - controller.abort(postponedValue);
452 - return <div>aborted</div>;
453 - }
454 -
455 - const errors = [];
456 - const result = await ReactDOMFizzStatic.prerenderToNodeStream(<App />, {
457 - onError: error => {
458 - errors.push(error);
459 - },
460 - signal: controller.signal,
461 - });
462 - await act(async () => {
463 - result.prelude.pipe(writable);
464 - });
465 - expect(getVisibleChildren(container)).toEqual(undefined);
466 - expect(errors).toEqual([]);
467 - });
468 -
403 // @gate enableHalt
404 it('will halt a prerender when aborting with an error during a render', async () => {
405 const controller = new AbortController();
packages/react/index.experimental.development.js
-1
@@ -30,7 +30,6 @@ export {
30 cacheSignal,
31 startTransition,
32 Activity,
33 - unstable_postpone,
33 unstable_getCacheForType,
34 unstable_SuspenseList,
35 ViewTransition,
packages/react/index.experimental.js
-1
@@ -31,7 +31,6 @@ export {
31 startTransition,
32 Activity,
33 Activity as unstable_Activity,
34 - unstable_postpone,
34 unstable_getCacheForType,
35 unstable_SuspenseList,
36 ViewTransition,
packages/react/src/ReactClient.js
-2
@@ -34,7 +34,6 @@ import {lazy} from './ReactLazy';
34 import {forwardRef} from './ReactForwardRef';
35 import {memo} from './ReactMemo';
36 import {cache, cacheSignal} from './ReactCacheClient';
37 -import {postpone} from './ReactPostpone';
37 import {
38 getCacheForType,
39 useCallback,
@@ -84,7 +83,6 @@ export {
83 memo,
84 cache,
85 cacheSignal,
87 - postpone as unstable_postpone,
86 useCallback,
87 useContext,
88 useEffect,
packages/react/src/ReactServer.experimental.development.js
-2
@@ -38,7 +38,6 @@ import {lazy} from './ReactLazy';
38 import {memo} from './ReactMemo';
39 import {cache, cacheSignal} from './ReactCacheServer';
40 import {startTransition} from './ReactStartTransition';
41 -import {postpone} from './ReactPostpone';
41 import {captureOwnerStack} from './ReactOwnerStack';
42 import version from 'shared/ReactVersion';
43
@@ -76,7 +75,6 @@ export {
75 cacheSignal,
76 startTransition,
77 getCacheForType as unstable_getCacheForType,
79 - postpone as unstable_postpone,
78 useId,
79 useCallback,
80 useDebugValue,
packages/react/src/ReactServer.experimental.js
-2
@@ -38,7 +38,6 @@ import {lazy} from './ReactLazy';
38 import {memo} from './ReactMemo';
39 import {cache, cacheSignal} from './ReactCacheServer';
40 import {startTransition} from './ReactStartTransition';
41 -import {postpone} from './ReactPostpone';
41 import version from 'shared/ReactVersion';
42
43 const Children = {
@@ -75,7 +74,6 @@ export {
74 cacheSignal,
75 startTransition,
76 getCacheForType as unstable_getCacheForType,
78 - postpone as unstable_postpone,
77 useId,
78 useCallback,
79 useDebugValue,
packages/shared/ReactFeatureFlags.js
+1 -1
@@ -80,7 +80,7 @@ export const enableAsyncIterableChildren = __EXPERIMENTAL__;
80
81 export const enableTaint = __EXPERIMENTAL__;
82
83 -export const enablePostpone = __EXPERIMENTAL__;
83 +export const enablePostpone: boolean = false; // Probably won't ship in this form.
84
85 export const enableHalt: boolean = true;
86