Skip to content

Commit ccc3a53

Browse files
committed
Scaffolding for <Catch>
Exposes the <Catch> component (behind a flag) and teachs Fiber/Fizz to recognize it. Basically everything except the actual error handling behavior; it gets rendered as a fragment for now.
1 parent 3e3401f commit ccc3a53

File tree

17 files changed

+193
-1
lines changed

17 files changed

+193
-1
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ let ReactDOMClient;
2525
let ReactDOMFizzServer;
2626
let Suspense;
2727
let SuspenseList;
28+
let Catch;
29+
let createCatch;
2830
let useSyncExternalStore;
2931
let useSyncExternalStoreWithSelector;
3032
let use;
@@ -79,6 +81,8 @@ describe('ReactDOMFizzServer', () => {
7981
ReactDOMFizzServer = require('react-dom/server');
8082
Stream = require('stream');
8183
Suspense = React.Suspense;
84+
Catch = React.experimental_Catch;
85+
createCatch = React.experimental_createCatch;
8286
use = React.use;
8387
if (gate(flags => flags.enableSuspenseList)) {
8488
SuspenseList = React.SuspenseList;
@@ -1201,6 +1205,35 @@ describe('ReactDOMFizzServer', () => {
12011205
expect(ref.current).toBe(b);
12021206
});
12031207

1208+
// TODO: This API is not fully implemented yet. This test just confirms that
1209+
// the API is exposed and the component type is recognized by React.
1210+
// @gate enableCreateCatch
1211+
it('renders <Catch> boundary', async () => {
1212+
const TypedCatch = createCatch();
1213+
1214+
function App() {
1215+
return (
1216+
<Catch>
1217+
<TypedCatch>
1218+
<Text text="Hi!" />
1219+
</TypedCatch>
1220+
</Catch>
1221+
);
1222+
}
1223+
1224+
await act(() => {
1225+
const {pipe} = renderToPipeableStream(<App />);
1226+
pipe(writable);
1227+
});
1228+
1229+
expect(getVisibleChildren(container)).toEqual('Hi!');
1230+
1231+
await clientAct(() => {
1232+
ReactDOMClient.hydrateRoot(container, <App />);
1233+
});
1234+
expect(getVisibleChildren(container)).toEqual('Hi!');
1235+
});
1236+
12041237
// @gate enableSuspenseList
12051238
it('shows inserted items before pending in a SuspenseList as fallbacks while hydrating', async () => {
12061239
const ref = React.createRef();

packages/react-reconciler/src/ReactFiber.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
enableDebugTracing,
4040
enableFloat,
4141
enableHostSingletons,
42+
enableCreateCatch,
4243
} from 'shared/ReactFeatureFlags';
4344
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
4445
import {ConcurrentRoot} from './ReactRootTags';
@@ -69,6 +70,7 @@ import {
6970
LegacyHiddenComponent,
7071
CacheComponent,
7172
TracingMarkerComponent,
73+
CatchComponent,
7274
} from './ReactWorkTags';
7375
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
7476
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
@@ -105,6 +107,8 @@ import {
105107
REACT_LEGACY_HIDDEN_TYPE,
106108
REACT_CACHE_TYPE,
107109
REACT_TRACING_MARKER_TYPE,
110+
REACT_CATCH_TYPE,
111+
REACT_TYPED_CATCH_TYPE,
108112
} from 'shared/ReactSymbols';
109113
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent';
110114
import {
@@ -583,6 +587,13 @@ export function createFiberFromTypeAndProps(
583587
// This is a consumer
584588
fiberTag = ContextConsumer;
585589
break getTag;
590+
case REACT_CATCH_TYPE:
591+
case REACT_TYPED_CATCH_TYPE:
592+
if (!enableCreateCatch) {
593+
throw new Error('Not implemented.');
594+
}
595+
fiberTag = CatchComponent;
596+
break getTag;
586597
case REACT_FORWARD_REF_TYPE:
587598
fiberTag = ForwardRef;
588599
if (__DEV__) {

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import {
7474
LegacyHiddenComponent,
7575
CacheComponent,
7676
TracingMarkerComponent,
77+
CatchComponent,
7778
} from './ReactWorkTags';
7879
import {
7980
NoFlags,
@@ -111,6 +112,7 @@ import {
111112
enableHostSingletons,
112113
enableFormActions,
113114
enableAsyncActions,
115+
enableCreateCatch,
114116
} from 'shared/ReactFeatureFlags';
115117
import isArray from 'shared/isArray';
116118
import shallowEqual from 'shared/shallowEqual';
@@ -1426,6 +1428,20 @@ function finishClassComponent(
14261428
return workInProgress.child;
14271429
}
14281430

1431+
function updateCatchComponent(
1432+
current: Fiber | null,
1433+
workInProgress: Fiber,
1434+
renderLanes: Lanes,
1435+
): Fiber | null {
1436+
if (!enableCreateCatch || !(workInProgress.mode & ConcurrentMode)) {
1437+
throw new Error('Not implemented.');
1438+
}
1439+
// TODO: Catch is not implemented yet.
1440+
const nextChildren = workInProgress.pendingProps.children;
1441+
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1442+
return workInProgress.child;
1443+
}
1444+
14291445
function pushHostRootContext(workInProgress: Fiber) {
14301446
const root = (workInProgress.stateNode: FiberRoot);
14311447
if (root.pendingContext) {
@@ -4277,6 +4293,12 @@ function beginWork(
42774293
}
42784294
break;
42794295
}
4296+
case CatchComponent: {
4297+
if (enableCreateCatch) {
4298+
return updateCatchComponent(current, workInProgress, renderLanes);
4299+
}
4300+
break;
4301+
}
42804302
}
42814303

42824304
throw new Error(

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import {
7272
LegacyHiddenComponent,
7373
CacheComponent,
7474
TracingMarkerComponent,
75+
CatchComponent,
7576
} from './ReactWorkTags';
7677
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
7778
import {
@@ -178,6 +179,7 @@ import {
178179
popRootMarkerInstance,
179180
} from './ReactFiberTracingMarkerComponent';
180181
import {suspendCommit} from './ReactFiberThenable';
182+
import {enableCreateCatch} from '../../shared/ReactFeatureFlags';
181183

182184
function markUpdate(workInProgress: Fiber) {
183185
// Tag the fiber with an update effect. This turns a Placement into
@@ -1868,6 +1870,12 @@ function completeWork(
18681870
}
18691871
return null;
18701872
}
1873+
case CatchComponent: {
1874+
if (enableCreateCatch) {
1875+
bubbleProperties(workInProgress);
1876+
return null;
1877+
}
1878+
}
18711879
}
18721880

18731881
throw new Error(

packages/react-reconciler/src/ReactWorkTags.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export type WorkTag =
3535
| 24
3636
| 25
3737
| 26
38-
| 27;
38+
| 27
39+
| 28;
3940

4041
export const FunctionComponent = 0;
4142
export const ClassComponent = 1;
@@ -64,3 +65,4 @@ export const CacheComponent = 24;
6465
export const TracingMarkerComponent = 25;
6566
export const HostHoistable = 26;
6667
export const HostSingleton = 27;
68+
export const CatchComponent = 28;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
let React;
2+
let ReactNoop;
3+
let Scheduler;
4+
let act;
5+
let assertLog;
6+
let Catch;
7+
let createCatch;
8+
9+
describe('ReactCatch', () => {
10+
beforeEach(() => {
11+
jest.resetModules();
12+
13+
React = require('react');
14+
ReactNoop = require('react-noop-renderer');
15+
Scheduler = require('scheduler');
16+
act = require('internal-test-utils').act;
17+
assertLog = require('internal-test-utils').assertLog;
18+
Catch = React.experimental_Catch;
19+
createCatch = React.experimental_createCatch;
20+
});
21+
22+
function Text({text}) {
23+
Scheduler.log(text);
24+
return text;
25+
}
26+
27+
// TODO: This API is not fully implemented yet. This test just confirms that
28+
// the API is exposed and the component type is recognized by React.
29+
// @gate enableCreateCatch
30+
test('<Catch> API exists', async () => {
31+
const TypedCatch = createCatch();
32+
33+
const root = ReactNoop.createRoot();
34+
await act(() => {
35+
root.render(
36+
<Catch>
37+
<TypedCatch>
38+
<Text text="Hi!" />
39+
</TypedCatch>
40+
</Catch>,
41+
);
42+
});
43+
assertLog(['Hi!']);
44+
expect(root).toMatchRenderedOutput('Hi!');
45+
});
46+
});

packages/react-server/src/ReactFizzServer.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ import {
129129
REACT_SERVER_CONTEXT_TYPE,
130130
REACT_SCOPE_TYPE,
131131
REACT_OFFSCREEN_TYPE,
132+
REACT_CATCH_TYPE,
133+
REACT_TYPED_CATCH_TYPE,
132134
} from 'shared/ReactSymbols';
133135
import ReactSharedInternals from 'shared/ReactSharedInternals';
134136
import {
@@ -1275,6 +1277,11 @@ function renderElement(
12751277
renderLazyComponent(request, task, prevThenableState, type, props);
12761278
return;
12771279
}
1280+
case REACT_CATCH_TYPE:
1281+
case REACT_TYPED_CATCH_TYPE: {
1282+
renderNodeDestructive(request, task, null, props.children);
1283+
return;
1284+
}
12781285
}
12791286
}
12801287

packages/react/index.classic.fb.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ export {
2020
SuspenseList,
2121
SuspenseList as unstable_SuspenseList, // TODO: Remove once call sights updated to SuspenseList
2222
cloneElement,
23+
experimental_Catch,
24+
experimental_createCatch,
25+
experimental_raise,
2326
createContext,
2427
createElement,
2528
createFactory,

packages/react/index.experimental.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export {
1919
Suspense,
2020
SuspenseList,
2121
cloneElement,
22+
experimental_Catch,
23+
experimental_createCatch,
24+
experimental_raise,
2225
createContext,
2326
createElement,
2427
createFactory,

packages/react/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export {
4242
Suspense,
4343
SuspenseList,
4444
cloneElement,
45+
experimental_Catch,
46+
experimental_createCatch,
47+
experimental_raise,
4548
createContext,
4649
createElement,
4750
createFactory,

0 commit comments

Comments
 (0)