@@ -49,6 +49,8 @@ let renderPhaseUpdates: Map<UpdateQueue<any>, Update<any>> | null = null;
49
49
let numberOfReRenders : number = 0 ;
50
50
const RE_RENDER_LIMIT = 25 ;
51
51
52
+ let isInHookUserCodeInDev = false ;
53
+
52
54
// In DEV, this is the name of the currently executing primitive hook
53
55
let currentHookNameInDev : ?string ;
54
56
@@ -57,6 +59,14 @@ function resolveCurrentlyRenderingComponent(): Object {
57
59
currentlyRenderingComponent !== null ,
58
60
'Hooks can only be called inside the body of a function component.' ,
59
61
) ;
62
+ if ( __DEV__ ) {
63
+ warning (
64
+ ! isInHookUserCodeInDev ,
65
+ 'Hooks can only be called inside the body of a function component. ' +
66
+ 'Do not call Hooks inside other Hooks. For more information, see ' +
67
+ 'https://fb.me/rules-of-hooks' ,
68
+ ) ;
69
+ }
60
70
return currentlyRenderingComponent ;
61
71
}
62
72
@@ -137,6 +147,9 @@ function createWorkInProgressHook(): Hook {
137
147
138
148
export function prepareToUseHooks ( componentIdentity : Object ) : void {
139
149
currentlyRenderingComponent = componentIdentity ;
150
+ if ( __DEV__ ) {
151
+ isInHookUserCodeInDev = false ;
152
+ }
140
153
141
154
// The following should have already been reset
142
155
// didScheduleRenderPhaseUpdate = false;
@@ -173,6 +186,9 @@ export function finishHooks(
173
186
numberOfReRenders = 0 ;
174
187
renderPhaseUpdates = null ;
175
188
workInProgressHook = null ;
189
+ if ( __DEV__ ) {
190
+ isInHookUserCodeInDev = false ;
191
+ }
176
192
177
193
// These were reset above
178
194
// currentlyRenderingComponent = null;
@@ -191,6 +207,15 @@ function readContext<T>(
191
207
): T {
192
208
let threadID = currentThreadID ;
193
209
validateContextBounds ( context , threadID ) ;
210
+ if ( __DEV__ ) {
211
+ warning (
212
+ ! isInHookUserCodeInDev ,
213
+ 'Context can only be read while React is rendering. ' +
214
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
215
+ 'In function components, you can read it directly in the function body, but not ' +
216
+ 'inside Hooks like useReducer() or useMemo().' ,
217
+ ) ;
218
+ }
194
219
return context [ threadID ] ;
195
220
}
196
221
@@ -234,7 +259,7 @@ export function useReducer<S, A>(
234
259
currentHookNameInDev = 'useReducer' ;
235
260
}
236
261
}
237
- let component = ( currentlyRenderingComponent = resolveCurrentlyRenderingComponent ( ) ) ;
262
+ currentlyRenderingComponent = resolveCurrentlyRenderingComponent ( ) ;
238
263
workInProgressHook = createWorkInProgressHook ( ) ;
239
264
if ( isReRender ) {
240
265
// This is a re-render. Apply the new render phase updates to the previous
@@ -253,10 +278,13 @@ export function useReducer<S, A>(
253
278
// priority because it will always be the same as the current
254
279
// render's.
255
280
const action = update . action ;
256
- // Temporarily clear to forbid calling Hooks.
257
- currentlyRenderingComponent = null ;
281
+ if ( __DEV__ ) {
282
+ isInHookUserCodeInDev = true ;
283
+ }
258
284
newState = reducer ( newState , action ) ;
259
- currentlyRenderingComponent = component ;
285
+ if ( __DEV__ ) {
286
+ isInHookUserCodeInDev = false ;
287
+ }
260
288
update = update . next ;
261
289
} while ( update !== null ) ;
262
290
@@ -267,7 +295,9 @@ export function useReducer<S, A>(
267
295
}
268
296
return [ workInProgressHook . memoizedState , dispatch ] ;
269
297
} else {
270
- currentlyRenderingComponent = null ;
298
+ if ( __DEV__ ) {
299
+ isInHookUserCodeInDev = true ;
300
+ }
271
301
if (reducer === basicStateReducer) {
272
302
// Special case for `useState`.
273
303
if ( typeof initialState === 'function' ) {
@@ -276,7 +306,9 @@ export function useReducer<S, A>(
276
306
} else if ( initialAction !== undefined && initialAction !== null ) {
277
307
initialState = reducer ( initialState , initialAction ) ;
278
308
}
279
- currentlyRenderingComponent = component;
309
+ if (__DEV__) {
310
+ isInHookUserCodeInDev = false ;
311
+ }
280
312
workInProgressHook.memoizedState = initialState;
281
313
const queue: UpdateQueue< A > = (workInProgressHook.queue = {
282
314
last : null ,
@@ -292,7 +324,7 @@ export function useReducer<S, A>(
292
324
}
293
325
294
326
function useMemo < T > (nextCreate: () => T , deps : Array < mixed > | void | null): T {
295
- let component = ( currentlyRenderingComponent = resolveCurrentlyRenderingComponent ( ) ) ;
327
+ currentlyRenderingComponent = resolveCurrentlyRenderingComponent ( ) ;
296
328
workInProgressHook = createWorkInProgressHook ( ) ;
297
329
298
330
const nextDeps = deps === undefined ? null : deps ;
@@ -309,10 +341,13 @@ function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
309
341
}
310
342
}
311
343
312
- // Temporarily clear to forbid calling Hooks.
313
- currentlyRenderingComponent = null ;
344
+ if ( __DEV__ ) {
345
+ isInHookUserCodeInDev = true ;
346
+ }
314
347
const nextValue = nextCreate ( ) ;
315
- currentlyRenderingComponent = component ;
348
+ if ( __DEV__ ) {
349
+ isInHookUserCodeInDev = false ;
350
+ }
316
351
workInProgressHook . memoizedState = [ nextValue , nextDeps ] ;
317
352
return nextValue ;
318
353
}
0 commit comments