[Fizz] Implement Hooks (#21257)
* Implement Fizz Hooks This is pretty much just a copy of the partial renderer Hooks. * Implement forward ref and memo
Sebastian Markbåge committed
Apr 14, 2021 at 17:16 UTC
dde875dfb1f84692ed02f82b0fc781bec9a864ef
2 files changed
+555
-56
packages/react-server/src/ReactFizzHooks.js
new
+515
@@ -0,0 +1,515 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
11
+
12
+import type {
13
+ MutableSource,
14
+ MutableSourceGetSnapshotFn,
15
+ MutableSourceSubscribeFn,
16
+ ReactContext,
17
+} from 'shared/ReactTypes';
18
+
19
+import {readContext as readContextImpl} from './ReactFizzNewContext';
20
+
21
+import invariant from 'shared/invariant';
22
+import {enableCache} from 'shared/ReactFeatureFlags';
23
+import is from 'shared/objectIs';
24
+
25
+type BasicStateAction<S> = (S => S) | S;
26
+type Dispatch<A> = A => void;
27
+
28
+type Update<A> = {|
29
+ action: A,
30
+ next: Update<A> | null,
31
+|};
32
+
33
+type UpdateQueue<A> = {|
34
+ last: Update<A> | null,
35
+ dispatch: any,
36
+|};
37
+
38
+type Hook = {|
39
+ memoizedState: any,
40
+ queue: UpdateQueue<any> | null,
41
+ next: Hook | null,
42
+|};
43
+
44
+type OpaqueIDType = string;
45
+
46
+let currentlyRenderingComponent: Object | null = null;
47
+let firstWorkInProgressHook: Hook | null = null;
48
+let workInProgressHook: Hook | null = null;
49
+// Whether the work-in-progress hook is a re-rendered hook
50
+let isReRender: boolean = false;
51
+// Whether an update was scheduled during the currently executing render pass.
52
+let didScheduleRenderPhaseUpdate: boolean = false;
53
+// Lazily created map of render-phase updates
54
+let renderPhaseUpdates: Map<UpdateQueue<any>, Update<any>> | null = null;
55
+// Counter to prevent infinite loops.
56
+let numberOfReRenders: number = 0;
57
+const RE_RENDER_LIMIT = 25;
58
+
59
+let isInHookUserCodeInDev = false;
60
+
61
+// In DEV, this is the name of the currently executing primitive hook
62
+let currentHookNameInDev: ?string;
63
+
64
+function resolveCurrentlyRenderingComponent(): Object {
65
+ invariant(
66
+ currentlyRenderingComponent !== null,
67
+ 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
68
+ ' one of the following reasons:\n' +
69
+ '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
70
+ '2. You might be breaking the Rules of Hooks\n' +
71
+ '3. You might have more than one copy of React in the same app\n' +
72
+ 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
73
+ );
74
+ if (__DEV__) {
75
+ if (isInHookUserCodeInDev) {
76
+ console.error(
77
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
78
+ 'You can only call Hooks at the top level of your React function. ' +
79
+ 'For more information, see ' +
80
+ 'https://reactjs.org/link/rules-of-hooks',
81
+ );
82
+ }
83
+ }
84
+ return currentlyRenderingComponent;
85
+}
86
+
87
+function areHookInputsEqual(
88
+ nextDeps: Array<mixed>,
89
+ prevDeps: Array<mixed> | null,
90
+) {
91
+ if (prevDeps === null) {
92
+ if (__DEV__) {
93
+ console.error(
94
+ '%s received a final argument during this render, but not during ' +
95
+ 'the previous render. Even though the final argument is optional, ' +
96
+ 'its type cannot change between renders.',
97
+ currentHookNameInDev,
98
+ );
99
+ }
100
+ return false;
101
+ }
102
+
103
+ if (__DEV__) {
104
+ // Don't bother comparing lengths in prod because these arrays should be
105
+ // passed inline.
106
+ if (nextDeps.length !== prevDeps.length) {
107
+ console.error(
108
+ 'The final argument passed to %s changed size between renders. The ' +
109
+ 'order and size of this array must remain constant.\n\n' +
110
+ 'Previous: %s\n' +
111
+ 'Incoming: %s',
112
+ currentHookNameInDev,
113
+ `[${nextDeps.join(', ')}]`,
114
+ `[${prevDeps.join(', ')}]`,
115
+ );
116
+ }
117
+ }
118
+ for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
119
+ if (is(nextDeps[i], prevDeps[i])) {
120
+ continue;
121
+ }
122
+ return false;
123
+ }
124
+ return true;
125
+}
126
+
127
+function createHook(): Hook {
128
+ if (numberOfReRenders > 0) {
129
+ invariant(false, 'Rendered more hooks than during the previous render');
130
+ }
131
+ return {
132
+ memoizedState: null,
133
+ queue: null,
134
+ next: null,
135
+ };
136
+}
137
+
138
+function createWorkInProgressHook(): Hook {
139
+ if (workInProgressHook === null) {
140
+ // This is the first hook in the list
141
+ if (firstWorkInProgressHook === null) {
142
+ isReRender = false;
143
+ firstWorkInProgressHook = workInProgressHook = createHook();
144
+ } else {
145
+ // There's already a work-in-progress. Reuse it.
146
+ isReRender = true;
147
+ workInProgressHook = firstWorkInProgressHook;
148
+ }
149
+ } else {
150
+ if (workInProgressHook.next === null) {
151
+ isReRender = false;
152
+ // Append to the end of the list
153
+ workInProgressHook = workInProgressHook.next = createHook();
154
+ } else {
155
+ // There's already a work-in-progress. Reuse it.
156
+ isReRender = true;
157
+ workInProgressHook = workInProgressHook.next;
158
+ }
159
+ }
160
+ return workInProgressHook;
161
+}
162
+
163
+export function prepareToUseHooks(componentIdentity: Object): void {
164
+ currentlyRenderingComponent = componentIdentity;
165
+ if (__DEV__) {
166
+ isInHookUserCodeInDev = false;
167
+ }
168
+
169
+ // The following should have already been reset
170
+ // didScheduleRenderPhaseUpdate = false;
171
+ // firstWorkInProgressHook = null;
172
+ // numberOfReRenders = 0;
173
+ // renderPhaseUpdates = null;
174
+ // workInProgressHook = null;
175
+}
176
+
177
+export function finishHooks(
178
+ Component: any,
179
+ props: any,
180
+ children: any,
181
+ refOrContext: any,
182
+): any {
183
+ // This must be called after every function component to prevent hooks from
184
+ // being used in classes.
185
+
186
+ while (didScheduleRenderPhaseUpdate) {
187
+ // Updates were scheduled during the render phase. They are stored in
188
+ // the `renderPhaseUpdates` map. Call the component again, reusing the
189
+ // work-in-progress hooks and applying the additional updates on top. Keep
190
+ // restarting until no more updates are scheduled.
191
+ didScheduleRenderPhaseUpdate = false;
192
+ numberOfReRenders += 1;
193
+
194
+ // Start over from the beginning of the list
195
+ workInProgressHook = null;
196
+
197
+ children = Component(props, refOrContext);
198
+ }
199
+ resetHooksState();
200
+ return children;
201
+}
202
+
203
+// Reset the internal hooks state if an error occurs while rendering a component
204
+export function resetHooksState(): void {
205
+ if (__DEV__) {
206
+ isInHookUserCodeInDev = false;
207
+ }
208
+
209
+ currentlyRenderingComponent = null;
210
+ didScheduleRenderPhaseUpdate = false;
211
+ firstWorkInProgressHook = null;
212
+ numberOfReRenders = 0;
213
+ renderPhaseUpdates = null;
214
+ workInProgressHook = null;
215
+}
216
+
217
+function getCacheForType<T>(resourceType: () => T): T {
218
+ // TODO: This should silently mark this as client rendered since it's not necesssarily
219
+ // considered an error. It needs to work for things like Flight though.
220
+ invariant(false, 'Not implemented.');
221
+}
222
+
223
+function readContext<T>(context: ReactContext<T>): T {
224
+ if (__DEV__) {
225
+ if (isInHookUserCodeInDev) {
226
+ console.error(
227
+ 'Context can only be read while React is rendering. ' +
228
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
229
+ 'In function components, you can read it directly in the function body, but not ' +
230
+ 'inside Hooks like useReducer() or useMemo().',
231
+ );
232
+ }
233
+ }
234
+ return readContextImpl(context);
235
+}
236
+
237
+function useContext<T>(context: ReactContext<T>): T {
238
+ if (__DEV__) {
239
+ currentHookNameInDev = 'useContext';
240
+ }
241
+ resolveCurrentlyRenderingComponent();
242
+ return readContextImpl(context);
243
+}
244
+
245
+function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
246
+ // $FlowFixMe: Flow doesn't like mixed types
247
+ return typeof action === 'function' ? action(state) : action;
248
+}
249
+
250
+export function useState<S>(
251
+ initialState: (() => S) | S,
252
+): [S, Dispatch<BasicStateAction<S>>] {
253
+ if (__DEV__) {
254
+ currentHookNameInDev = 'useState';
255
+ }
256
+ return useReducer(
257
+ basicStateReducer,
258
+ // useReducer has a special case to support lazy useState initializers
259
+ (initialState: any),
260
+ );
261
+}
262
+
263
+export function useReducer<S, I, A>(
264
+ reducer: (S, A) => S,
265
+ initialArg: I,
266
+ init?: I => S,
267
+): [S, Dispatch<A>] {
268
+ if (__DEV__) {
269
+ if (reducer !== basicStateReducer) {
270
+ currentHookNameInDev = 'useReducer';
271
+ }
272
+ }
273
+ currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
274
+ workInProgressHook = createWorkInProgressHook();
275
+ if (isReRender) {
276
+ // This is a re-render. Apply the new render phase updates to the previous
277
+ // current hook.
278
+ const queue: UpdateQueue<A> = (workInProgressHook.queue: any);
279
+ const dispatch: Dispatch<A> = (queue.dispatch: any);
280
+ if (renderPhaseUpdates !== null) {
281
+ // Render phase updates are stored in a map of queue -> linked list
282
+ const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
283
+ if (firstRenderPhaseUpdate !== undefined) {
284
+ renderPhaseUpdates.delete(queue);
285
+ let newState = workInProgressHook.memoizedState;
286
+ let update = firstRenderPhaseUpdate;
287
+ do {
288
+ // Process this render phase update. We don't have to check the
289
+ // priority because it will always be the same as the current
290
+ // render's.
291
+ const action = update.action;
292
+ if (__DEV__) {
293
+ isInHookUserCodeInDev = true;
294
+ }
295
+ newState = reducer(newState, action);
296
+ if (__DEV__) {
297
+ isInHookUserCodeInDev = false;
298
+ }
299
+ update = update.next;
300
+ } while (update !== null);
301
+
302
+ workInProgressHook.memoizedState = newState;
303
+
304
+ return [newState, dispatch];
305
+ }
306
+ }
307
+ return [workInProgressHook.memoizedState, dispatch];
308
+ } else {
309
+ if (__DEV__) {
310
+ isInHookUserCodeInDev = true;
311
+ }
312
+ let initialState;
313
+ if (reducer === basicStateReducer) {
314
+ // Special case for `useState`.
315
+ initialState =
316
+ typeof initialArg === 'function'
317
+ ? ((initialArg: any): () => S)()
318
+ : ((initialArg: any): S);
319
+ } else {
320
+ initialState =
321
+ init !== undefined ? init(initialArg) : ((initialArg: any): S);
322
+ }
323
+ if (__DEV__) {
324
+ isInHookUserCodeInDev = false;
325
+ }
326
+ workInProgressHook.memoizedState = initialState;
327
+ const queue: UpdateQueue<A> = (workInProgressHook.queue = {
328
+ last: null,
329
+ dispatch: null,
330
+ });
331
+ const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
332
+ null,
333
+ currentlyRenderingComponent,
334
+ queue,
335
+ ): any));
336
+ return [workInProgressHook.memoizedState, dispatch];
337
+ }
338
+}
339
+
340
+function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
341
+ currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
342
+ workInProgressHook = createWorkInProgressHook();
343
+
344
+ const nextDeps = deps === undefined ? null : deps;
345
+
346
+ if (workInProgressHook !== null) {
347
+ const prevState = workInProgressHook.memoizedState;
348
+ if (prevState !== null) {
349
+ if (nextDeps !== null) {
350
+ const prevDeps = prevState[1];
351
+ if (areHookInputsEqual(nextDeps, prevDeps)) {
352
+ return prevState[0];
353
+ }
354
+ }
355
+ }
356
+ }
357
+
358
+ if (__DEV__) {
359
+ isInHookUserCodeInDev = true;
360
+ }
361
+ const nextValue = nextCreate();
362
+ if (__DEV__) {
363
+ isInHookUserCodeInDev = false;
364
+ }
365
+ workInProgressHook.memoizedState = [nextValue, nextDeps];
366
+ return nextValue;
367
+}
368
+
369
+function useRef<T>(initialValue: T): {|current: T|} {
370
+ currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
371
+ workInProgressHook = createWorkInProgressHook();
372
+ const previousRef = workInProgressHook.memoizedState;
373
+ if (previousRef === null) {
374
+ const ref = {current: initialValue};
375
+ if (__DEV__) {
376
+ Object.seal(ref);
377
+ }
378
+ workInProgressHook.memoizedState = ref;
379
+ return ref;
380
+ } else {
381
+ return previousRef;
382
+ }
383
+}
384
+
385
+export function useLayoutEffect(
386
+ create: () => (() => void) | void,
387
+ inputs: Array<mixed> | void | null,
388
+) {
389
+ if (__DEV__) {
390
+ currentHookNameInDev = 'useLayoutEffect';
391
+ console.error(
392
+ 'useLayoutEffect does nothing on the server, because its effect cannot ' +
393
+ "be encoded into the server renderer's output format. This will lead " +
394
+ 'to a mismatch between the initial, non-hydrated UI and the intended ' +
395
+ 'UI. To avoid this, useLayoutEffect should only be used in ' +
396
+ 'components that render exclusively on the client. ' +
397
+ 'See https://reactjs.org/link/uselayouteffect-ssr for common fixes.',
398
+ );
399
+ }
400
+}
401
+
402
+function dispatchAction<A>(
403
+ componentIdentity: Object,
404
+ queue: UpdateQueue<A>,
405
+ action: A,
406
+) {
407
+ invariant(
408
+ numberOfReRenders < RE_RENDER_LIMIT,
409
+ 'Too many re-renders. React limits the number of renders to prevent ' +
410
+ 'an infinite loop.',
411
+ );
412
+
413
+ if (componentIdentity === currentlyRenderingComponent) {
414
+ // This is a render phase update. Stash it in a lazily-created map of
415
+ // queue -> linked list of updates. After this render pass, we'll restart
416
+ // and apply the stashed updates on top of the work-in-progress hook.
417
+ didScheduleRenderPhaseUpdate = true;
418
+ const update: Update<A> = {
419
+ action,
420
+ next: null,
421
+ };
422
+ if (renderPhaseUpdates === null) {
423
+ renderPhaseUpdates = new Map();
424
+ }
425
+ const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
426
+ if (firstRenderPhaseUpdate === undefined) {
427
+ renderPhaseUpdates.set(queue, update);
428
+ } else {
429
+ // Append the update to the end of the list.
430
+ let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
431
+ while (lastRenderPhaseUpdate.next !== null) {
432
+ lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
433
+ }
434
+ lastRenderPhaseUpdate.next = update;
435
+ }
436
+ } else {
437
+ // This means an update has happened after the function component has
438
+ // returned. On the server this is a no-op. In React Fiber, the update
439
+ // would be scheduled for a future render.
440
+ }
441
+}
442
+
443
+export function useCallback<T>(
444
+ callback: T,
445
+ deps: Array<mixed> | void | null,
446
+): T {
447
+ return useMemo(() => callback, deps);
448
+}
449
+
450
+// TODO Decide on how to implement this hook for server rendering.
451
+// If a mutation occurs during render, consider triggering a Suspense boundary
452
+// and falling back to client rendering.
453
+function useMutableSource<Source, Snapshot>(
454
+ source: MutableSource<Source>,
455
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
456
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
457
+): Snapshot {
458
+ resolveCurrentlyRenderingComponent();
459
+ return getSnapshot(source._source);
460
+}
461
+
462
+function useDeferredValue<T>(value: T): T {
463
+ resolveCurrentlyRenderingComponent();
464
+ return value;
465
+}
466
+
467
+function unsupportedStartTransition() {
468
+ invariant(false, 'startTransition cannot be called during server rendering.');
469
+}
470
+
471
+function useTransition(): [(callback: () => void) => void, boolean] {
472
+ resolveCurrentlyRenderingComponent();
473
+ return [unsupportedStartTransition, false];
474
+}
475
+
476
+function useOpaqueIdentifier(): OpaqueIDType {
477
+ throw new Error('Not yet implemented.');
478
+}
479
+
480
+function unsupportedRefresh() {
481
+ invariant(false, 'Cache cannot be refreshed during server rendering.');
482
+}
483
+
484
+function useCacheRefresh(): <T>(?() => T, ?T) => void {
485
+ return unsupportedRefresh;
486
+}
487
+
488
+function noop(): void {}
489
+
490
+export const Dispatcher: DispatcherType = {
491
+ readContext,
492
+ useContext,
493
+ useMemo,
494
+ useReducer,
495
+ useRef,
496
+ useState,
497
+ useLayoutEffect,
498
+ useCallback,
499
+ // useImperativeHandle is not run in the server environment
500
+ useImperativeHandle: noop,
501
+ // Effects are not run in the server environment.
502
+ useEffect: noop,
503
+ // Debugging effect
504
+ useDebugValue: noop,
505
+ useDeferredValue,
506
+ useTransition,
507
+ useOpaqueIdentifier,
508
+ // Subscriptions are not setup in a server environment.
509
+ useMutableSource,
510
+};
511
+
512
+if (enableCache) {
513
+ Dispatcher.getCacheForType = getCacheForType;
514
+ Dispatcher.useCacheRefresh = useCacheRefresh;
515
+}
packages/react-server/src/ReactFizzServer.js
+40
-56
@@ -7,7 +7,6 @@
7
* @flow
8
*/
9
10
-import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
10
import type {
11
Destination,
12
Chunk,
@@ -70,6 +69,12 @@ import {
69
pushProvider,
70
popProvider,
71
} from './ReactFizzNewContext';
72
+import {
73
+ prepareToUseHooks,
74
+ finishHooks,
75
+ resetHooksState,
76
+ Dispatcher,
77
+} from './ReactFizzHooks';
78
79
import {
80
getIteratorFn,
@@ -199,6 +204,8 @@ function defaultErrorHandler(error: mixed) {
204
console['error'](error); // Don't transform to our wrapper
205
}
206
207
+function noop(): void {}
208
+
209
export function createRequest(
210
children: ReactNodeList,
211
destination: Destination,
@@ -474,8 +481,10 @@ function renderWithHooks<Props, SecondArg>(
481
props: Props,
482
secondArg: SecondArg,
483
): any {
477
- // TODO: Set up Hooks etc.
478
- const children = Component(props, secondArg);
484
+ const componentIdentity = {};
485
+ prepareToUseHooks(componentIdentity);
486
+ const result = Component(props, secondArg);
487
+ const children = finishHooks(Component, props, result, secondArg);
488
if (children === undefined) {
489
invalidRenderResult(Component);
490
}
@@ -719,13 +728,29 @@ function validateFunctionComponentInDev(Component: any): void {
728
}
729
}
730
731
+function resolveDefaultProps(Component: any, baseProps: Object): Object {
732
+ if (Component && Component.defaultProps) {
733
+ // Resolve default props. Taken from ReactElement
734
+ const props = Object.assign({}, baseProps);
735
+ const defaultProps = Component.defaultProps;
736
+ for (const propName in defaultProps) {
737
+ if (props[propName] === undefined) {
738
+ props[propName] = defaultProps[propName];
739
+ }
740
+ }
741
+ return props;
742
+ }
743
+ return baseProps;
744
+}
745
+
746
function renderForwardRef(
747
request: Request,
748
task: Task,
749
type: any,
750
props: Object,
751
+ ref: any,
752
): void {
728
- throw new Error('Not yet implemented element type.');
753
+ renderWithHooks(request, task, type, props, ref);
754
}
755
756
function renderMemo(
@@ -733,8 +758,11 @@ function renderMemo(
758
task: Task,
759
type: any,
760
props: Object,
761
+ ref: any,
762
): void {
737
- throw new Error('Not yet implemented element type.');
763
+ const innerType = type.type;
764
+ const resolvedProps = resolveDefaultProps(innerType, props);
765
+ renderElement(request, task, innerType, resolvedProps, ref);
766
}
767
768
function renderContextConsumer(
@@ -826,6 +854,7 @@ function renderElement(
854
task: Task,
855
type: any,
856
props: Object,
857
+ ref: any,
858
): void {
859
if (typeof type === 'function') {
860
if (shouldConstruct(type)) {
@@ -868,11 +897,11 @@ function renderElement(
897
if (typeof type === 'object' && type !== null) {
898
switch (type.$$typeof) {
899
case REACT_FORWARD_REF_TYPE: {
871
- renderForwardRef(request, task, type, props);
900
+ renderForwardRef(request, task, type, props, ref);
901
return;
902
}
903
case REACT_MEMO_TYPE: {
875
- renderMemo(request, task, type, props);
904
+ renderMemo(request, task, type, props, ref);
905
return;
906
}
907
case REACT_PROVIDER_TYPE: {
@@ -952,7 +981,8 @@ function renderNodeDestructive(
981
const element: React$Element<any> = (node: any);
982
const type = element.type;
983
const props = element.props;
955
- renderElement(request, task, type, props);
984
+ const ref = element.ref;
985
+ renderElement(request, task, type, props, ref);
986
return;
987
}
988
case REACT_PORTAL_TYPE:
@@ -1102,6 +1132,7 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
1132
try {
1133
return renderNodeDestructive(request, task, node);
1134
} catch (x) {
1135
+ resetHooksState();
1136
if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
1137
spawnNewSuspendedTask(request, task, x);
1138
// Restore the context. We assume that this will be restored by the inner
@@ -1290,6 +1321,7 @@ function retryTask(request: Request, task: Task): void {
1321
segment.status = COMPLETED;
1322
finishedTask(request, task.blockedBoundary, segment);
1323
} catch (x) {
1324
+ resetHooksState();
1325
if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
1326
// Something suspended again, let's pick it back up later.
1327
const ping = task.ping;
@@ -1704,51 +1736,3 @@ export function abort(request: Request): void {
1736
fatalError(request, error);
1737
}
1738
}
1707
-
1708
-function notYetImplemented(): void {
1709
- throw new Error('Not yet implemented.');
1710
-}
1711
-
1712
-function unsupportedRefresh() {
1713
- invariant(false, 'Cache cannot be refreshed during server rendering.');
1714
-}
1715
-
1716
-function unsupportedStartTransition() {
1717
- invariant(false, 'startTransition cannot be called during server rendering.');
1718
-}
1719
-
1720
-function noop(): void {}
1721
-
1722
-const Dispatcher: DispatcherType = {
1723
- useMemo<T>(nextCreate: () => T): T {
1724
- return nextCreate();
1725
- },
1726
- useCallback<T>(callback: T): T {
1727
- return callback;
1728
- },
1729
- useDebugValue(): void {},
1730
- useDeferredValue<T>(value: T): T {
1731
- return value;
1732
- },
1733
- useTransition(): [(callback: () => void) => void, boolean] {
1734
- return [unsupportedStartTransition, false];
1735
- },
1736
- getCacheForType<T>(resourceType: () => T): T {
1737
- throw new Error('Not yet implemented. Should mark as client rendered.');
1738
- },
1739
- readContext: (notYetImplemented: any),
1740
- useContext: (notYetImplemented: any),
1741
- useReducer: (notYetImplemented: any),
1742
- useRef: (notYetImplemented: any),
1743
- useState: (notYetImplemented: any),
1744
- useLayoutEffect: noop,
1745
- // useImperativeHandle is not run in the server environment
1746
- useImperativeHandle: noop,
1747
- // Effects are not run in the server environment.
1748
- useEffect: noop,
1749
- useOpaqueIdentifier: (notYetImplemented: any),
1750
- useMutableSource: (notYetImplemented: any),
1751
- useCacheRefresh(): <T>(?() => T, ?T) => void {
1752
- return unsupportedRefresh;
1753
- },
1754
-};