Skip to content

Commit 2cbea24

Browse files
committed
[compiler][fixtures] Patch error-handling edge case in snap evaluator
Fix edge case in which we incorrectly returned a cached exception instead of trying to rerender with new props. ghstack-source-id: 843fb85 Pull Request resolved: #31082
1 parent 5d12e9e commit 2cbea24

File tree

2 files changed

+32
-15
lines changed

2 files changed

+32
-15
lines changed

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/throw-before-scope-starts.expect.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const FIXTURE_ENTRYPOINT = {
7575
7676
### Eval output
7777
(kind: ok) [[ (exception in render) Error: throw with error! ]]
78-
[[ (exception in render) Error: throw with error! ]]
78+
[2]
7979
[[ (exception in render) Error: throw with error! ]]
8080
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
8181
[null]

compiler/packages/snap/src/sprout/evaluator.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,38 +60,55 @@ const ExportSchema = z.object({
6060
FIXTURE_ENTRYPOINT: EntrypointSchema,
6161
});
6262

63+
const NO_ERROR_SENTINEL = Symbol();
6364
/**
6465
* Wraps WrapperTestComponent in an error boundary to simplify re-rendering
6566
* when an exception is thrown.
6667
* A simpler alternative may be to re-mount test components manually.
6768
*/
6869
class WrapperTestComponentWithErrorBoundary extends React.Component<
6970
{fn: any; params: Array<any>},
70-
{hasError: boolean; error: any}
71+
{errorFromLastRender: any}
7172
> {
72-
propsErrorMap: MutableRefObject<Map<any, any>>;
73+
/**
74+
* Limit retries of the child component by caching seen errors.
75+
*/
76+
propsErrorMap: Map<any, any>;
77+
lastProps: any | null;
78+
// lastProps: object | null;
7379
constructor(props: any) {
7480
super(props);
75-
this.state = {hasError: false, error: null};
76-
this.propsErrorMap = React.createRef() as MutableRefObject<Map<any, any>>;
77-
this.propsErrorMap.current = new Map();
81+
this.lastProps = null;
82+
this.propsErrorMap = new Map<any, any>();
83+
this.state = {
84+
errorFromLastRender: NO_ERROR_SENTINEL,
85+
};
7886
}
7987
static getDerivedStateFromError(error: any) {
80-
return {hasError: true, error: error};
88+
// Reschedule a second render that immediately returns the cached error
89+
return {errorFromLastRender: error};
8190
}
8291
override componentDidUpdate() {
83-
if (this.state.hasError) {
84-
this.setState({hasError: false, error: null});
92+
if (this.state.errorFromLastRender !== NO_ERROR_SENTINEL) {
93+
// Reschedule a third render that immediately returns the cached error
94+
this.setState({errorFromLastRender: NO_ERROR_SENTINEL});
8595
}
8696
}
8797
override render() {
88-
if (this.state.hasError) {
89-
this.propsErrorMap.current!.set(
90-
this.props,
91-
`[[ (exception in render) ${this.state.error?.toString()} ]]`,
92-
);
98+
if (
99+
this.state.errorFromLastRender !== NO_ERROR_SENTINEL &&
100+
this.props === this.lastProps
101+
) {
102+
/**
103+
* The last render errored, cache the error message to avoid running the
104+
* test fixture more than once
105+
*/
106+
const errorMsg = `[[ (exception in render) ${this.state.errorFromLastRender?.toString()} ]]`;
107+
this.propsErrorMap.set(this.lastProps, errorMsg);
108+
return errorMsg;
93109
}
94-
const cachedError = this.propsErrorMap.current!.get(this.props);
110+
this.lastProps = this.props;
111+
const cachedError = this.propsErrorMap.get(this.props);
95112
if (cachedError != null) {
96113
return cachedError;
97114
}

0 commit comments

Comments
 (0)