diff --git a/.eslintrc.js b/.eslintrc.js index eaad9393c5685..09c3414b6897b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -450,6 +450,13 @@ module.exports = { __IS_EDGE__: 'readonly', }, }, + { + files: ['packages/react-devtools-shell/**/*.js'], + rules: { + // DevTools shell is an internal app not published to NPM + 'react-internal/prod-error-codes': 'off', + }, + }, ], env: { diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index e8dc2578be11a..cb46092380337 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -12,6 +12,7 @@ import type { ReactContext, ReactProviderType, StartTransitionOptions, + Thenable, Usable, } from 'shared/ReactTypes'; import type { @@ -122,14 +123,33 @@ function readContext(context: ReactContext): T { return context._currentValue; } +function useThenable(thenable: Thenable): T { + switch (thenable.status) { + case 'fulfilled': + return thenable.value; + case 'rejected': + throw thenable.reason; + case 'pending': + default: + throw thenable; + } +} + function use(usable: Usable): T { if (usable !== null && typeof usable === 'object') { // $FlowFixMe[method-unbinding] if (typeof usable.then === 'function') { - // TODO: What should this do if it receives an unresolved promise? - throw new Error( - 'Support for `use(Promise)` not yet implemented in react-debug-tools.', - ); + // This is a thenable. + const thenable: Thenable = (usable: any); + const value = useThenable(thenable); + + hookLog.push({ + primitive: 'Use', + stackError: new Error(), + value, + }); + + return value; } else if (usable.$$typeof === REACT_CONTEXT_TYPE) { const context: ReactContext = (usable: any); const value = readContext(context); diff --git a/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js b/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js index aa3a9a6295731..d9e6de30a54d5 100644 --- a/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js +++ b/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js @@ -13,6 +13,7 @@ import { forwardRef, Fragment, memo, + Suspense, useCallback, useContext, useDebugValue, @@ -20,6 +21,7 @@ import { useOptimistic, useState, use, + useReducer, } from 'react'; import {useFormState} from 'react-dom'; @@ -132,6 +134,55 @@ function Forms() { ); } +let promise = Promise.resolve('initial'); +function FunctionWithUse() { + // $FlowFixMe[underconstrained-implicit-instantiation] + const [, rerender] = useReducer((n: number) => n + 1, 0); + const value = use(promise); + + return ( + + {value} +
{ + event.preventDefault(); + const delay = +event.target.elements.delay.value; + const shouldReject = event.target.elements.shouldReject.checked; + const settledValue = event.target.elements.settledValue.value; + promise = new Promise((resolve, reject) => { + setTimeout(() => { + if (shouldReject) { + reject(new Error(settledValue)); + } else { + resolve(settledValue); + } + }, delay); + }); + rerender(); + }}> + + + + +
+
+ ); +} + export default function CustomHooks(): React.Node { return ( @@ -140,6 +191,9 @@ export default function CustomHooks(): React.Node { + + + ); }