ReactHooks.js
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
10import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
11import type {
12 ReactContext,
13 StartTransitionOptions,
14 Usable,
15 Awaited,
16} from 'shared/ReactTypes';
17import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';
18
19import ReactSharedInternals from 'shared/ReactSharedInternals';
20
21type BasicStateAction<S> = (S => S) | S;
22type Dispatch<A> = A => void;
23
24function resolveDispatcher() {
25 const dispatcher = ReactSharedInternals.H;
26 if (__DEV__) {
27 if (dispatcher === null) {
28 console.error(
29 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
30 ' one of the following reasons:\n' +
31 '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
32 '2. You might be breaking the Rules of Hooks\n' +
33 '3. You might have more than one copy of React in the same app\n' +
34 'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
35 );
36 }
37 }
38 // Will result in a null access error if accessed outside render phase. We
39 // intentionally don't throw our own error because this is in a hot path.
40 // Also helps ensure this is inlined.
41 return ((dispatcher: any): Dispatcher);
42}
43
44export function getCacheForType<T>(resourceType: () => T): T {
45 const dispatcher = ReactSharedInternals.A;
46 if (!dispatcher) {
47 // If there is no dispatcher, then we treat this as not being cached.
48 return resourceType();
49 }
50 return dispatcher.getCacheForType(resourceType);
51}
52
53export function useContext<T>(Context: ReactContext<T>): T {
54 const dispatcher = resolveDispatcher();
55 if (__DEV__) {
56 if (Context.$$typeof === REACT_CONSUMER_TYPE) {
57 console.error(
58 'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' +
59 'Did you mean to call useContext(Context) instead?',
60 );
61 }
62 }
63 return dispatcher.useContext(Context);
64}
65
66export function useState<S>(
67 initialState: (() => S) | S,
68): [S, Dispatch<BasicStateAction<S>>] {
69 const dispatcher = resolveDispatcher();
70 return dispatcher.useState(initialState);
71}
72
73export function useReducer<S, I, A>(
74 reducer: (S, A) => S,
75 initialArg: I,
76 init?: I => S,
77): [S, Dispatch<A>] {
78 const dispatcher = resolveDispatcher();
79 return dispatcher.useReducer(reducer, initialArg, init);
80}
81
82export function useRef<T>(initialValue: T): {current: T} {
83 const dispatcher = resolveDispatcher();
84 return dispatcher.useRef(initialValue);
85}
86
87export function useEffect(
88 create: () => (() => void) | void,
89 deps: Array<mixed> | void | null,
90): void {
91 if (__DEV__) {
92 if (create == null) {
93 console.warn(
94 'React Hook useEffect requires an effect callback. Did you forget to pass a callback to the hook?',
95 );
96 }
97 }
98
99 const dispatcher = resolveDispatcher();
100 return dispatcher.useEffect(create, deps);
101}
102
103export function useInsertionEffect(
104 create: () => (() => void) | void,
105 deps: Array<mixed> | void | null,
106): void {
107 if (__DEV__) {
108 if (create == null) {
109 console.warn(
110 'React Hook useInsertionEffect requires an effect callback. Did you forget to pass a callback to the hook?',
111 );
112 }
113 }
114
115 const dispatcher = resolveDispatcher();
116 return dispatcher.useInsertionEffect(create, deps);
117}
118
119export function useLayoutEffect(
120 create: () => (() => void) | void,
121 deps: Array<mixed> | void | null,
122): void {
123 if (__DEV__) {
124 if (create == null) {
125 console.warn(
126 'React Hook useLayoutEffect requires an effect callback. Did you forget to pass a callback to the hook?',
127 );
128 }
129 }
130
131 const dispatcher = resolveDispatcher();
132 return dispatcher.useLayoutEffect(create, deps);
133}
134
135export function useCallback<T>(
136 callback: T,
137 deps: Array<mixed> | void | null,
138): T {
139 const dispatcher = resolveDispatcher();
140 return dispatcher.useCallback(callback, deps);
141}
142
143export function useMemo<T>(
144 create: () => T,
145 deps: Array<mixed> | void | null,
146): T {
147 const dispatcher = resolveDispatcher();
148 return dispatcher.useMemo(create, deps);
149}
150
151export function useImperativeHandle<T>(
152 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
153 create: () => T,
154 deps: Array<mixed> | void | null,
155): void {
156 const dispatcher = resolveDispatcher();
157 return dispatcher.useImperativeHandle(ref, create, deps);
158}
159
160export function useDebugValue<T>(
161 value: T,
162 formatterFn: ?(value: T) => mixed,
163): void {
164 if (__DEV__) {
165 const dispatcher = resolveDispatcher();
166 return dispatcher.useDebugValue(value, formatterFn);
167 }
168}
169
170export function useTransition(): [
171 boolean,
172 (callback: () => void, options?: StartTransitionOptions) => void,
173] {
174 const dispatcher = resolveDispatcher();
175 return dispatcher.useTransition();
176}
177
178export function useDeferredValue<T>(value: T, initialValue?: T): T {
179 const dispatcher = resolveDispatcher();
180 return dispatcher.useDeferredValue(value, initialValue);
181}
182
183export function useId(): string {
184 const dispatcher = resolveDispatcher();
185 return dispatcher.useId();
186}
187
188export function useSyncExternalStore<T>(
189 subscribe: (() => void) => () => void,
190 getSnapshot: () => T,
191 getServerSnapshot?: () => T,
192): T {
193 const dispatcher = resolveDispatcher();
194 return dispatcher.useSyncExternalStore(
195 subscribe,
196 getSnapshot,
197 getServerSnapshot,
198 );
199}
200
201export function useCacheRefresh(): <T>(?() => T, ?T) => void {
202 const dispatcher = resolveDispatcher();
203 // $FlowFixMe[not-a-function] This is unstable, thus optional
204 return dispatcher.useCacheRefresh();
205}
206
207export function use<T>(usable: Usable<T>): T {
208 const dispatcher = resolveDispatcher();
209 return dispatcher.use(usable);
210}
211
212export function useMemoCache(size: number): Array<mixed> {
213 const dispatcher = resolveDispatcher();
214 // $FlowFixMe[not-a-function] This is unstable, thus optional
215 return dispatcher.useMemoCache(size);
216}
217
218export function useEffectEvent<Args, F: (...Array<Args>) => mixed>(
219 callback: F,
220): F {
221 const dispatcher = resolveDispatcher();
222 // $FlowFixMe[not-a-function] This is unstable, thus optional
223 return dispatcher.useEffectEvent(callback);
224}
225
226export function useOptimistic<S, A>(
227 passthrough: S,
228 reducer: ?(S, A) => S,
229): [S, (A) => void] {
230 const dispatcher = resolveDispatcher();
231 return dispatcher.useOptimistic(passthrough, reducer);
232}
233
234export function useActionState<S, P>(
235 action: (Awaited<S>, P) => S,
236 initialState: Awaited<S>,
237 permalink?: string,
238): [Awaited<S>, (P) => void, boolean] {
239 const dispatcher = resolveDispatcher();
240 return dispatcher.useActionState(action, initialState, permalink);
241}