Skip to content

Scaffolding for <Catch> #26854

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ let ReactDOMClient;
let ReactDOMFizzServer;
let Suspense;
let SuspenseList;
let Catch;
let createCatch;
let useSyncExternalStore;
let useSyncExternalStoreWithSelector;
let use;
Expand Down Expand Up @@ -79,6 +81,8 @@ describe('ReactDOMFizzServer', () => {
ReactDOMFizzServer = require('react-dom/server');
Stream = require('stream');
Suspense = React.Suspense;
Catch = React.experimental_Catch;
createCatch = React.experimental_createCatch;
use = React.use;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
Expand Down Expand Up @@ -1201,6 +1205,35 @@ describe('ReactDOMFizzServer', () => {
expect(ref.current).toBe(b);
});

// TODO: This API is not fully implemented yet. This test just confirms that
// the API is exposed and the component type is recognized by React.
// @gate enableCreateCatch
it('renders <Catch> boundary', async () => {
const TypedCatch = createCatch();

function App() {
return (
<Catch>
<TypedCatch>
<Text text="Hi!" />
</TypedCatch>
</Catch>
);
}

await act(() => {
const {pipe} = renderToPipeableStream(<App />);
pipe(writable);
});

expect(getVisibleChildren(container)).toEqual('Hi!');

await clientAct(() => {
ReactDOMClient.hydrateRoot(container, <App />);
});
expect(getVisibleChildren(container)).toEqual('Hi!');
});

// @gate enableSuspenseList
it('shows inserted items before pending in a SuspenseList as fallbacks while hydrating', async () => {
const ref = React.createRef();
Expand Down
17 changes: 17 additions & 0 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
enableDebugTracing,
enableFloat,
enableHostSingletons,
enableCreateCatch,
} from 'shared/ReactFeatureFlags';
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
import {ConcurrentRoot} from './ReactRootTags';
Expand Down Expand Up @@ -69,6 +70,8 @@ import {
LegacyHiddenComponent,
CacheComponent,
TracingMarkerComponent,
CatchComponent,
TypedCatchComponent,
} from './ReactWorkTags';
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
Expand Down Expand Up @@ -105,6 +108,8 @@ import {
REACT_LEGACY_HIDDEN_TYPE,
REACT_CACHE_TYPE,
REACT_TRACING_MARKER_TYPE,
REACT_CATCH_TYPE,
REACT_TYPED_CATCH_TYPE,
} from 'shared/ReactSymbols';
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent';
import {
Expand Down Expand Up @@ -573,6 +578,12 @@ export function createFiberFromTypeAndProps(
break;
}
// Fall through
case REACT_CATCH_TYPE:
if (enableCreateCatch) {
fiberTag = CatchComponent;
break;
}
// Fall through
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
Expand All @@ -583,6 +594,12 @@ export function createFiberFromTypeAndProps(
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
case REACT_TYPED_CATCH_TYPE:
if (enableCreateCatch) {
fiberTag = TypedCatchComponent;
break getTag;
}
// Fall through
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
if (__DEV__) {
Expand Down
24 changes: 24 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ import {
LegacyHiddenComponent,
CacheComponent,
TracingMarkerComponent,
CatchComponent,
TypedCatchComponent,
} from './ReactWorkTags';
import {
NoFlags,
Expand Down Expand Up @@ -111,6 +113,7 @@ import {
enableHostSingletons,
enableFormActions,
enableAsyncActions,
enableCreateCatch,
} from 'shared/ReactFeatureFlags';
import isArray from 'shared/isArray';
import shallowEqual from 'shared/shallowEqual';
Expand Down Expand Up @@ -1426,6 +1429,20 @@ function finishClassComponent(
return workInProgress.child;
}

function updateCatchComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (!enableCreateCatch || !(workInProgress.mode & ConcurrentMode)) {
throw new Error('Not implemented.');
}
// TODO: Catch is not implemented yet.
const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

function pushHostRootContext(workInProgress: Fiber) {
const root = (workInProgress.stateNode: FiberRoot);
if (root.pendingContext) {
Expand Down Expand Up @@ -4277,6 +4294,13 @@ function beginWork(
}
break;
}
case CatchComponent:
case TypedCatchComponent: {
if (enableCreateCatch) {
return updateCatchComponent(current, workInProgress, renderLanes);
}
break;
}
}

throw new Error(
Expand Down
10 changes: 10 additions & 0 deletions packages/react-reconciler/src/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ import {
LegacyHiddenComponent,
CacheComponent,
TracingMarkerComponent,
CatchComponent,
TypedCatchComponent,
} from './ReactWorkTags';
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
import {
Expand Down Expand Up @@ -178,6 +180,7 @@ import {
popRootMarkerInstance,
} from './ReactFiberTracingMarkerComponent';
import {suspendCommit} from './ReactFiberThenable';
import {enableCreateCatch} from '../../shared/ReactFeatureFlags';

function markUpdate(workInProgress: Fiber) {
// Tag the fiber with an update effect. This turns a Placement into
Expand Down Expand Up @@ -1868,6 +1871,13 @@ function completeWork(
}
return null;
}
case CatchComponent:
case TypedCatchComponent: {
if (enableCreateCatch) {
bubbleProperties(workInProgress);
return null;
}
}
}

throw new Error(
Expand Down
6 changes: 5 additions & 1 deletion packages/react-reconciler/src/ReactWorkTags.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export type WorkTag =
| 24
| 25
| 26
| 27;
| 27
| 28
| 29;

export const FunctionComponent = 0;
export const ClassComponent = 1;
Expand Down Expand Up @@ -64,3 +66,5 @@ export const CacheComponent = 24;
export const TracingMarkerComponent = 25;
export const HostHoistable = 26;
export const HostSingleton = 27;
export const CatchComponent = 28;
export const TypedCatchComponent = 29;
46 changes: 46 additions & 0 deletions packages/react-reconciler/src/__tests__/ReactCatch-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
let React;
let ReactNoop;
let Scheduler;
let act;
let assertLog;
let Catch;
let createCatch;

describe('ReactCatch', () => {
beforeEach(() => {
jest.resetModules();

React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
act = require('internal-test-utils').act;
assertLog = require('internal-test-utils').assertLog;
Catch = React.experimental_Catch;
createCatch = React.experimental_createCatch;
});

function Text({text}) {
Scheduler.log(text);
return text;
}

// TODO: This API is not fully implemented yet. This test just confirms that
// the API is exposed and the component type is recognized by React.
// @gate enableCreateCatch
test('<Catch> API exists', async () => {
const TypedCatch = createCatch();

const root = ReactNoop.createRoot();
await act(() => {
root.render(
<Catch>
<TypedCatch>
<Text text="Hi!" />
</TypedCatch>
</Catch>,
);
});
assertLog(['Hi!']);
expect(root).toMatchRenderedOutput('Hi!');
});
});
15 changes: 15 additions & 0 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ import {
REACT_SERVER_CONTEXT_TYPE,
REACT_SCOPE_TYPE,
REACT_OFFSCREEN_TYPE,
REACT_CATCH_TYPE,
REACT_TYPED_CATCH_TYPE,
} from 'shared/ReactSymbols';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
Expand All @@ -138,6 +140,7 @@ import {
enableSuspenseAvoidThisFallbackFizz,
enableFloat,
enableCache,
enableCreateCatch,
} from 'shared/ReactFeatureFlags';

import assign from 'shared/assign';
Expand Down Expand Up @@ -1251,6 +1254,12 @@ function renderElement(
}
return;
}
case REACT_CATCH_TYPE: {
if (enableCreateCatch) {
renderNodeDestructive(request, task, null, props.children);
return;
}
}
}

if (typeof type === 'object' && type !== null) {
Expand All @@ -1275,6 +1284,12 @@ function renderElement(
renderLazyComponent(request, task, prevThenableState, type, props);
return;
}
case REACT_TYPED_CATCH_TYPE: {
if (enableCreateCatch) {
renderNodeDestructive(request, task, null, props.children);
return;
}
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/react/index.classic.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export {
SuspenseList,
SuspenseList as unstable_SuspenseList, // TODO: Remove once call sights updated to SuspenseList
cloneElement,
experimental_Catch,
experimental_createCatch,
experimental_raise,
createContext,
createElement,
createFactory,
Expand Down
3 changes: 3 additions & 0 deletions packages/react/index.experimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export {
Suspense,
SuspenseList,
cloneElement,
experimental_Catch,
experimental_createCatch,
experimental_raise,
createContext,
createElement,
createFactory,
Expand Down
3 changes: 3 additions & 0 deletions packages/react/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export {
Suspense,
SuspenseList,
cloneElement,
experimental_Catch,
experimental_createCatch,
experimental_raise,
createContext,
createElement,
createFactory,
Expand Down
3 changes: 3 additions & 0 deletions packages/react/index.modern.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export {
SuspenseList,
SuspenseList as unstable_SuspenseList, // TODO: Remove once call sights updated to SuspenseList
cloneElement,
experimental_Catch,
experimental_createCatch,
experimental_raise,
createContext,
createElement,
createMutableSource,
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/React.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
cloneElement as cloneElementProd,
isValidElement,
} from './ReactElement';
import {Catch, createCatch, raise} from './ReactCatch';
import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
import {forwardRef} from './ReactForwardRef';
Expand Down Expand Up @@ -97,6 +98,9 @@ export {
createRef,
Component,
PureComponent,
Catch as experimental_Catch,
createCatch as experimental_createCatch,
raise as experimental_raise,
createContext,
createServerContext,
forwardRef,
Expand Down
29 changes: 29 additions & 0 deletions packages/react/src/ReactCatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {ReactCatch} from 'shared/ReactTypes';

import {REACT_CATCH_TYPE, REACT_TYPED_CATCH_TYPE} from 'shared/ReactSymbols';
import {enableCreateCatch} from 'shared/ReactFeatureFlags';

export const Catch = REACT_CATCH_TYPE;

export function createCatch<T>(): ReactCatch<T> {
if (!enableCreateCatch) {
throw new Error('Not implemented.');
}
const catchType = {
$$typeof: REACT_TYPED_CATCH_TYPE,
};
return __DEV__ ? Object.freeze(catchType) : catchType;
}

export function raise<T>(catchType: ReactCatch<T>, value: T): empty {
throw new Error('Not implemented.');
}
1 change: 1 addition & 0 deletions packages/react/src/ReactSharedSubset.experimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export {
lazy,
memo,
cache,
experimental_raise,
startTransition,
unstable_DebugTracingMode,
unstable_getCacheSignal,
Expand Down
Loading