@samitouri / QOS-React-1 / commits / 2cbea245cc

[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: 843fb85df4a2ae7a88f296104fb16b5f9a34c76e Pull Request resolved: https://github.com/facebook/react/pull/31082

Mofei Zhang committed Sep 30, 2024 at 12:24 UTC 2cbea245cca4044f02c4c231a7f86c8062074579
2 files changed +32 -15
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/throw-before-scope-starts.expect.md
+1 -1
@@ -75,7 +75,7 @@ export const FIXTURE_ENTRYPOINT = {
75
76 ### Eval output
77 (kind: ok) [[ (exception in render) Error: throw with error! ]]
78 -[[ (exception in render) Error: throw with error! ]]
78 +[2]
79 [[ (exception in render) Error: throw with error! ]]
80 [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
81 [null]
compiler/packages/snap/src/sprout/evaluator.ts
+31 -14
@@ -60,6 +60,7 @@ const ExportSchema = z.object({
60 FIXTURE_ENTRYPOINT: EntrypointSchema,
61 });
62
63 +const NO_ERROR_SENTINEL = Symbol();
64 /**
65 * Wraps WrapperTestComponent in an error boundary to simplify re-rendering
66 * when an exception is thrown.
@@ -67,31 +68,47 @@ const ExportSchema = z.object({
68 */
69 class WrapperTestComponentWithErrorBoundary extends React.Component<
70 {fn: any; params: Array<any>},
70 - {hasError: boolean; error: any}
71 + {errorFromLastRender: any}
72 > {
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;
79 constructor(props: any) {
80 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 + };
86 }
87 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};
90 }
91 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});
95 }
96 }
97 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;
109 }
94 - const cachedError = this.propsErrorMap.current!.get(this.props);
110 + this.lastProps = this.props;
111 + const cachedError = this.propsErrorMap.get(this.props);
112 if (cachedError != null) {
113 return cachedError;
114 }