[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
This commit is contained in:
Mofei Zhang
2024-09-30 12:35:16 -04:00
parent 5377012a9c
commit e8f03707a5
2 changed files with 32 additions and 15 deletions
@@ -75,7 +75,7 @@ export const FIXTURE_ENTRYPOINT = {
### Eval output
(kind: ok) [[ (exception in render) Error: throw with error! ]]
[[ (exception in render) Error: throw with error! ]]
[2]
[[ (exception in render) Error: throw with error! ]]
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
[null]
+31 -14
View File
@@ -60,6 +60,7 @@ const ExportSchema = z.object({
FIXTURE_ENTRYPOINT: EntrypointSchema,
});
const NO_ERROR_SENTINEL = Symbol();
/**
* Wraps WrapperTestComponent in an error boundary to simplify re-rendering
* when an exception is thrown.
@@ -67,31 +68,47 @@ const ExportSchema = z.object({
*/
class WrapperTestComponentWithErrorBoundary extends React.Component<
{fn: any; params: Array<any>},
{hasError: boolean; error: any}
{errorFromLastRender: any}
> {
propsErrorMap: MutableRefObject<Map<any, any>>;
/**
* Limit retries of the child component by caching seen errors.
*/
propsErrorMap: Map<any, any>;
lastProps: any | null;
// lastProps: object | null;
constructor(props: any) {
super(props);
this.state = {hasError: false, error: null};
this.propsErrorMap = React.createRef() as MutableRefObject<Map<any, any>>;
this.propsErrorMap.current = new Map();
this.lastProps = null;
this.propsErrorMap = new Map<any, any>();
this.state = {
errorFromLastRender: NO_ERROR_SENTINEL,
};
}
static getDerivedStateFromError(error: any) {
return {hasError: true, error: error};
// Reschedule a second render that immediately returns the cached error
return {errorFromLastRender: error};
}
override componentDidUpdate() {
if (this.state.hasError) {
this.setState({hasError: false, error: null});
if (this.state.errorFromLastRender !== NO_ERROR_SENTINEL) {
// Reschedule a third render that immediately returns the cached error
this.setState({errorFromLastRender: NO_ERROR_SENTINEL});
}
}
override render() {
if (this.state.hasError) {
this.propsErrorMap.current!.set(
this.props,
`[[ (exception in render) ${this.state.error?.toString()} ]]`,
);
if (
this.state.errorFromLastRender !== NO_ERROR_SENTINEL &&
this.props === this.lastProps
) {
/**
* The last render errored, cache the error message to avoid running the
* test fixture more than once
*/
const errorMsg = `[[ (exception in render) ${this.state.errorFromLastRender?.toString()} ]]`;
this.propsErrorMap.set(this.lastProps, errorMsg);
return errorMsg;
}
const cachedError = this.propsErrorMap.current!.get(this.props);
this.lastProps = this.props;
const cachedError = this.propsErrorMap.get(this.props);
if (cachedError != null) {
return cachedError;
}