163
let didWarnAboutMismatchedHooksForComponent;
164
let didWarnUncachedGetSnapshot: void | true;
165
let didWarnAboutUseWrappedInTryCatch;
166
+let didWarnAboutAsyncClientComponent;
167
if (__DEV__) {
168
didWarnAboutMismatchedHooksForComponent = new Set<string | null>();
169
didWarnAboutUseWrappedInTryCatch = new Set<string | null>();
170
+ didWarnAboutAsyncClientComponent = new Set<string | null>();
171
}
172
173
export type Hook = {
372
}
373
}
374
375
+function warnIfAsyncClientComponent(
376
+ Component: Function,
377
+ componentDoesIncludeHooks: boolean,
378
+) {
379
+ if (__DEV__) {
380
+ // This dev-only check only works for detecting native async functions,
381
+ // not transpiled ones. There's also a prod check that we use to prevent
382
+ // async client components from crashing the app; the prod one works even
383
+ // for transpiled async functions. Neither mechanism is completely
384
+ // bulletproof but together they cover the most common cases.
385
+ const isAsyncFunction =
386
+ // $FlowIgnore[method-unbinding]
387
+ Object.prototype.toString.call(Component) === '[object AsyncFunction]';
388
+ if (isAsyncFunction) {
389
+ // Encountered an async Client Component. This is not yet supported,
390
+ // except in certain constrained cases, like during a route navigation.
391
+ const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
392
+ if (!didWarnAboutAsyncClientComponent.has(componentName)) {
393
+ didWarnAboutAsyncClientComponent.add(componentName);
394
+
395
+ // Check if this is a sync update. We use the "root" render lanes here
396
+ // because the "subtree" render lanes may include additional entangled
397
+ // lanes related to revealing previously hidden content.
398
+ const root = getWorkInProgressRoot();
399
+ const rootRenderLanes = getWorkInProgressRootRenderLanes();
400
+ if (root !== null && includesBlockingLane(root, rootRenderLanes)) {
401
+ console.error(
402
+ 'async/await is not yet supported in Client Components, only ' +
403
+ 'Server Components. This error is often caused by accidentally ' +
404
+ "adding `'use client'` to a module that was originally written " +
405
+ 'for the server.',
406
+ );
407
+ } else {
408
+ // This is a concurrent (Transition, Retry, etc) render. We don't
409
+ // warn in these cases.
410
+ //
411
+ // However, Async Components are forbidden to include hooks, even
412
+ // during a transition, so let's check for that here.
413
+ //
414
+ // TODO: Add a corresponding warning to Server Components runtime.
415
+ if (componentDoesIncludeHooks) {
416
+ console.error(
417
+ 'Hooks are not supported inside an async component. This ' +
418
+ "error is often caused by accidentally adding `'use client'` " +
419
+ 'to a module that was originally written for the server.',
420
+ );
421
+ }
422
+ }
423
+ }
424
+ }
425
+ }
426
+}
427
+
428
function throwInvalidHookError() {
429
throw new Error(
430
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
609
}
610
}
611
557
- finishRenderingHooks(current, workInProgress);
612
+ finishRenderingHooks(current, workInProgress, Component);
613
614
return children;
615
}
616
562
-function finishRenderingHooks(current: Fiber | null, workInProgress: Fiber) {
563
- // We can assume the previous dispatcher is always this one, since we set it
564
- // at the beginning of the render phase and there's no re-entrance.
565
- ReactCurrentDispatcher.current = ContextOnlyDispatcher;
566
-
617
+function finishRenderingHooks<Props, SecondArg>(
618
+ current: Fiber | null,
619
+ workInProgress: Fiber,
620
+ Component: (p: Props, arg: SecondArg) => any,
621
+): void {
622
if (__DEV__) {
623
workInProgress._debugHookTypes = hookTypesDev;
624
+
625
+ const componentDoesIncludeHooks =
626
+ workInProgressHook !== null || thenableIndexCounter !== 0;
627
+ warnIfAsyncClientComponent(Component, componentDoesIncludeHooks);
628
}
629
630
+ // We can assume the previous dispatcher is always this one, since we set it
631
+ // at the beginning of the render phase and there's no re-entrance.
632
+ ReactCurrentDispatcher.current = ContextOnlyDispatcher;
633
+
634
// This check uses currentHook so that it works the same in DEV and prod bundles.
635
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
636
const didRenderTooFewHooks =
708
if (checkIfUseWrappedInTryCatch()) {
709
const componentName =
710
getComponentNameFromFiber(workInProgress) || 'Unknown';
648
- if (!didWarnAboutUseWrappedInTryCatch.has(componentName)) {
711
+ if (
712
+ !didWarnAboutUseWrappedInTryCatch.has(componentName) &&
713
+ // This warning also fires if you suspend with `use` inside an
714
+ // async component. Since we warn for that above, we'll silence this
715
+ // second warning by checking here.
716
+ !didWarnAboutAsyncClientComponent.has(componentName)
717
+ ) {
718
didWarnAboutUseWrappedInTryCatch.add(componentName);
719
console.error(
720
'`use` was called from inside a try/catch block. This is not allowed ' +
752
props,
753
secondArg,
754
);
686
- finishRenderingHooks(current, workInProgress);
755
+ finishRenderingHooks(current, workInProgress, Component);
756
return children;
757
}
758