main
js 170 lines 5.23 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and 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} from 'react-reconciler/src/ReactInternalTypes';
11 import type {Request} from './ReactFlightServer';
12 import type {Thenable, Usable, ReactComponentInfo} from 'shared/ReactTypes';
13 import type {ThenableState} from './ReactFlightThenable';
14 import {
15 REACT_MEMO_CACHE_SENTINEL,
16 REACT_CONTEXT_TYPE,
17 } from 'shared/ReactSymbols';
18 import {createThenableState, trackUsedThenable} from './ReactFlightThenable';
19 import {isClientReference} from './ReactFlightServerConfig';
20
21 let currentRequest = null;
22 let thenableIndexCounter = 0;
23 let thenableState = null;
24 let currentComponentDebugInfo = null;
25
26 export function prepareToUseHooksForRequest(request: Request) {
27 currentRequest = request;
28 }
29
30 export function resetHooksForRequest() {
31 currentRequest = null;
32 }
33
34 export function prepareToUseHooksForComponent(
35 prevThenableState: ThenableState | null,
36 componentDebugInfo: null | ReactComponentInfo,
37 ) {
38 thenableIndexCounter = 0;
39 thenableState = prevThenableState;
40 if (__DEV__) {
41 currentComponentDebugInfo = componentDebugInfo;
42 }
43 }
44
45 export function getThenableStateAfterSuspending(): ThenableState {
46 // If you use() to Suspend this should always exist but if you throw a Promise instead,
47 // which is not really supported anymore, it will be empty. We use the empty set as a
48 // marker to know if this was a replay of the same component or first attempt.
49 const state = thenableState || createThenableState();
50 if (__DEV__) {
51 // This is a hack but we stash the debug info here so that we don't need a completely
52 // different data structure just for this in DEV. Not too happy about it.
53 (state as any)._componentDebugInfo = currentComponentDebugInfo;
54 currentComponentDebugInfo = null;
55 }
56 thenableState = null;
57 return state;
58 }
59
60 export function getTrackedThenablesAfterRendering(): null | Array<
61 Thenable<any>,
62 > {
63 return thenableState;
64 }
65
66 export const HooksDispatcher: Dispatcher = {
67 readContext: unsupportedContext as any,
68
69 use,
70 useCallback<T>(callback: T): T {
71 return callback;
72 },
73 useContext: unsupportedContext as any,
74 useEffect: unsupportedHook as any,
75 useImperativeHandle: unsupportedHook as any,
76 useLayoutEffect: unsupportedHook as any,
77 useInsertionEffect: unsupportedHook as any,
78 useMemo<T>(nextCreate: () => T): T {
79 return nextCreate();
80 },
81 useReducer: unsupportedHook as any,
82 useRef: unsupportedHook as any,
83 useState: unsupportedHook as any,
84 useDebugValue(): void {},
85 useDeferredValue: unsupportedHook as any,
86 useTransition: unsupportedHook as any,
87 useSyncExternalStore: unsupportedHook as any,
88 useId,
89 useHostTransitionStatus: unsupportedHook as any,
90 useFormState: unsupportedHook as any,
91 useActionState: unsupportedHook as any,
92 useOptimistic: unsupportedHook as any,
93 useMemoCache(size: number): Array<any> {
94 const data = new Array<any>(size);
95 for (let i = 0; i < size; i++) {
96 data[i] = REACT_MEMO_CACHE_SENTINEL;
97 }
98 return data;
99 },
100 useCacheRefresh(): <T>(?() => T, ?T) => void {
101 return unsupportedRefresh;
102 },
103 useEffectEvent: unsupportedHook as any,
104 };
105
106 function unsupportedHook(): void {
107 throw new Error('This Hook is not supported in Server Components.');
108 }
109
110 function unsupportedRefresh(): void {
111 throw new Error(
112 'Refreshing the cache is not supported in Server Components.',
113 );
114 }
115
116 function unsupportedContext(): void {
117 throw new Error('Cannot read a Client Context from a Server Component.');
118 }
119
120 function useId(): string {
121 if (currentRequest === null) {
122 throw new Error('useId can only be used while React is rendering');
123 }
124 const id = currentRequest.identifierCount++;
125 // use 'S' for Flight components to distinguish from 'R' and 'r' in Fizz/Client
126 return '_' + currentRequest.identifierPrefix + 'S_' + id.toString(32) + '_';
127 }
128
129 function use<T>(usable: Usable<T>): T {
130 if (
131 // $FlowFixMe[invalid-compare]
132 (usable !== null && typeof usable === 'object') ||
133 typeof usable === 'function'
134 ) {
135 // $FlowFixMe[method-unbinding]
136 if (typeof usable.then === 'function') {
137 // This is a thenable.
138 const thenable: Thenable<T> = usable as any;
139
140 // Track the position of the thenable within this fiber.
141 const index = thenableIndexCounter;
142 thenableIndexCounter += 1;
143
144 if (thenableState === null) {
145 thenableState = createThenableState();
146 }
147 return trackUsedThenable(thenableState, thenable, index);
148 } else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
149 unsupportedContext();
150 }
151 }
152
153 if (isClientReference(usable)) {
154 const clientReference: any = usable;
155 if (
156 clientReference.value != null &&
157 clientReference.value.$$typeof === REACT_CONTEXT_TYPE
158 ) {
159 // Show a more specific message since it's a common mistake.
160 throw new Error('Cannot read a Client Context from a Server Component.');
161 } else {
162 throw new Error('Cannot use() an already resolved Client Reference.');
163 }
164 } else {
165 throw new Error(
166 // eslint-disable-next-line react-internal/safe-string-coercion
167 'An unsupported type was passed to use(): ' + String(usable),
168 );
169 }
170 }