Skip to content

Commit 2489101

Browse files
author
Sebastian Silbermann
committed
Port latest useFormState debug-tools impl
Basically just port facebook#28319 to avoid merge conflicts of this PR with main
1 parent 8fd1e99 commit 2489101

2 files changed

Lines changed: 100 additions & 11 deletions

File tree

packages/react-debug-tools/src/ReactDebugHooks.js

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -524,19 +524,62 @@ function useActionState<S, P>(
524524
// TODO: how to handle pending state?
525525
nextHook(); // PendingState
526526
nextHook(); // ActionQueue
527-
let state;
527+
const stackError = new Error();
528+
let value;
529+
let debugInfo = null;
530+
let error = null;
531+
528532
if (hook !== null) {
529-
state = hook.memoizedState;
533+
const actionResult = hook.memoizedState;
534+
if (
535+
typeof actionResult === 'object' &&
536+
actionResult !== null &&
537+
// $FlowFixMe[method-unbinding]
538+
typeof actionResult.then === 'function'
539+
) {
540+
const thenable: Thenable<Awaited<S>> = (actionResult: any);
541+
switch (thenable.status) {
542+
case 'fulfilled': {
543+
value = thenable.value;
544+
debugInfo =
545+
thenable._debugInfo === undefined ? null : thenable._debugInfo;
546+
break;
547+
}
548+
case 'rejected': {
549+
const rejectedError = thenable.reason;
550+
error = rejectedError;
551+
break;
552+
}
553+
default:
554+
// If this was an uncached Promise we have to abandon this attempt
555+
// but we can still emit anything up until this point.
556+
error = SuspenseException;
557+
debugInfo =
558+
thenable._debugInfo === undefined ? null : thenable._debugInfo;
559+
value = thenable;
560+
}
561+
} else {
562+
value = (actionResult: any);
563+
}
530564
} else {
531-
state = initialState;
565+
value = initialState;
532566
}
567+
533568
hookLog.push({
534569
displayName: null,
535570
primitive: 'ActionState',
536-
stackError: new Error(),
537-
value: state,
538-
debugInfo: null,
571+
stackError: stackError,
572+
value: value,
573+
debugInfo: debugInfo,
539574
});
575+
576+
if (error !== null) {
577+
throw error;
578+
}
579+
580+
// value being a Thenable is equivalent to error being not null
581+
// i.e. we only reach this point with Awaited<S>
582+
const state = ((value: any): Awaited<S>);
540583
return [state, (payload: P) => {}, false];
541584
}
542585

packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,62 @@ function wrapWithHoc(Component: (props: any, ref: React$Ref<any>) => any) {
121121
}
122122
const HocWithHooks = wrapWithHoc(FunctionWithHooks);
123123

124+
function incrementWithDelay(previousState: number, formData: FormData) {
125+
const incrementDelay = +formData.get('incrementDelay');
126+
const shouldReject = formData.get('shouldReject');
127+
const reason = formData.get('reason');
128+
129+
return new Promise((resolve, reject) => {
130+
setTimeout(() => {
131+
if (shouldReject) {
132+
reject(reason);
133+
} else {
134+
resolve(previousState + 1);
135+
}
136+
}, incrementDelay);
137+
});
138+
}
139+
124140
function Forms() {
125-
const [state, formAction] = useFormState((n: number, formData: FormData) => {
126-
return n + 1;
127-
}, 0);
141+
const [state, formAction] = useFormState<any, any>(incrementWithDelay, 0);
128142
return (
129143
<form>
130-
{state}
144+
State: {state}&nbsp;
145+
<label>
146+
delay:
147+
<input
148+
name="incrementDelay"
149+
defaultValue={5000}
150+
type="text"
151+
inputMode="numeric"
152+
/>
153+
</label>
154+
<label>
155+
Reject:
156+
<input name="reason" type="text" />
157+
<input name="shouldReject" type="checkbox" />
158+
</label>
131159
<button formAction={formAction}>Increment</button>
132160
</form>
133161
);
134162
}
135163

164+
class ErrorBoundary extends React.Component<{children?: React$Node}> {
165+
state: {error: any} = {error: null};
166+
static getDerivedStateFromError(error: mixed): {error: any} {
167+
return {error};
168+
}
169+
componentDidCatch(error: any, info: any) {
170+
console.error(error, info);
171+
}
172+
render(): any {
173+
if (this.state.error) {
174+
return <div>Error: {String(this.state.error)}</div>;
175+
}
176+
return this.props.children;
177+
}
178+
}
179+
136180
function Actions() {
137181
const [state, action, isPending] = useActionState(
138182
(n: number, formData: FormData) => {
@@ -156,7 +200,9 @@ export default function CustomHooks(): React.Node {
156200
<MemoWithHooks />
157201
<ForwardRefWithHooks />
158202
<HocWithHooks />
159-
<Forms />
203+
<ErrorBoundary>
204+
<Forms />
205+
</ErrorBoundary>
160206
<Actions />
161207
</Fragment>
162208
);

0 commit comments

Comments
 (0)