-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react): prevent recursive exposing fallback when fallback throw …
…error (#1409) 🚧 This PR could make a huge difference, so please help us make sure this change gets a very thorough review. 🚧 ```tsx const Throw = { Error: ({ message, after = 0, children }: PropsWithChildren<{ message: string; after?: number }>) => { const [isNeedThrow, setIsNeedThrow] = useState(after === 0) if (isNeedThrow) { throw new Error(message) } useTimeout(() => setIsNeedThrow(true), after) return <>{children}</> } } const Example = () => ( <ErrorBoundary fallback={() => <>This is expected</>}> <ErrorBoundary fallback={() => ( <Throw.Error message={ERROR_MESSAGE} after={100}> ErrorBoundary's fallback before error </Throw.Error> )} > <Throw.Error message={ERROR_MESSAGE} after={100}> ErrorBoundary's children before error </Throw.Error> </ErrorBoundary> </ErrorBoundary> ) ``` ## Problem: ErrorBoundary's fallback can't be treated by parent ErrorBoundary <!-- A clear and concise description of what this pr is about. --> Thrown Error in fallback will be caught by ErrorBoundary self and then expose fallback recursively 🥲 1. ErrorBoundary's children before error 2. ErrorBoundary's fallback before error 3. ErrorBoundary's fallback before error 4. ErrorBoundary's fallback before error 5. ... expose fallback self recursively ... ## Solution: When we meet thrown error in fallback of ErrorBoundary wrap it as InternalFallbackError, re-throw InternalFallbackError.fallbackError, if it is InternalFallbackError Thrown Error in fallback will be caught by parent ErrorBoundary 👍 1. ErrorBoundary's children before error 2. ErrorBoundary's fallback before error 3. This is expected ## PR Checklist - [x] I did below actions if need 1. I read the [Contributing Guide](https://github.com/toss/suspensive/blob/main/CONTRIBUTING.md) 5. I added documents and tests. --------- Co-authored-by: Lee HyunJae (whale) <[email protected]> Co-authored-by: lucas0530 <[email protected]> Co-authored-by: HYUNGU KANG <[email protected]> Co-authored-by: Brian Vaughn <[email protected]>
- Loading branch information
1 parent
59a809d
commit 124238c
Showing
8 changed files
with
176 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@suspensive/react": minor | ||
--- | ||
|
||
feat(react): prevent recursive expose fallback when fallback throw error |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
examples/visualization/src/app/react/ErrorBoundary/ErrorInFallback/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
'use client' | ||
|
||
import { ErrorBoundary as SuspensiveErrorBoundary } from '@suspensive/react' | ||
import { type PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react' | ||
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary' | ||
import { Area } from '~/components/uis' | ||
|
||
export const useTimeout = (fn: () => void, ms: number) => { | ||
const fnRef = useRef(fn) | ||
fnRef.current = fn | ||
const fnPreserved = useCallback(() => fnRef.current(), []) | ||
useEffect(() => { | ||
const id = setTimeout(fnPreserved, ms) | ||
return () => clearTimeout(id) | ||
}, [fnPreserved, ms]) | ||
} | ||
|
||
export const Throw = { | ||
Error: ({ message, after = 0, children }: PropsWithChildren<{ message: string; after?: number }>) => { | ||
const [isNeedThrow, setIsNeedThrow] = useState(after === 0) | ||
if (isNeedThrow) { | ||
throw new Error(message) | ||
} | ||
useTimeout(() => setIsNeedThrow(true), after) | ||
return <>{children}</> | ||
}, | ||
} | ||
|
||
export default function Page() { | ||
return ( | ||
<div> | ||
<Area title="@suspensive/react"> | ||
<SuspensiveErrorBoundary fallback={() => <>This is expected</>}> | ||
<SuspensiveErrorBoundary | ||
fallback={() => { | ||
console.log("@suspensive/react's ErrorBoundary fallback") | ||
return ( | ||
<Throw.Error message={'error message in fallback'} after={1000}> | ||
SuspensiveErrorBoundary's fallback before error | ||
</Throw.Error> | ||
) | ||
}} | ||
> | ||
<Throw.Error message={'error message in children'} after={1000}> | ||
SuspensiveErrorBoundary's children before error | ||
</Throw.Error> | ||
</SuspensiveErrorBoundary> | ||
</SuspensiveErrorBoundary> | ||
</Area> | ||
|
||
<Area title="react-error-boundary"> | ||
<ReactErrorBoundary fallbackRender={() => <>This is expected</>}> | ||
<ReactErrorBoundary | ||
fallbackRender={() => { | ||
console.log("react-error-boundary's ErrorBoundary fallback") | ||
return ( | ||
<Throw.Error message={'error message in fallback'} after={1000}> | ||
ReactErrorBoundary's fallback before error | ||
</Throw.Error> | ||
) | ||
}} | ||
> | ||
<Throw.Error message={'error message in children'} after={1000}> | ||
ReactErrorBoundary's children before error | ||
</Throw.Error> | ||
</ReactErrorBoundary> | ||
</ReactErrorBoundary> | ||
</Area> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.