-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: error-boundary, async-boundary 추가
- Loading branch information
1 parent
a694a64
commit bc42196
Showing
4 changed files
with
300 additions
and
0 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,55 @@ | ||
import { PropsWithChildren, Suspense, useCallback } from "react"; | ||
|
||
import { useQueryErrorResetBoundary } from "@tanstack/react-query"; | ||
import ErrorBoundary, { | ||
ErrorBoundaryCommonProps, | ||
ErrorBoundaryWithErrorFallback, | ||
ErrorBoundaryWithErrorFallbackRender, | ||
} from "./error-boundary"; | ||
|
||
type ErrorBoundaryFallbackProps = | ||
| ErrorBoundaryWithErrorFallback | ||
| ErrorBoundaryWithErrorFallbackRender; | ||
|
||
type Props = ErrorBoundaryFallbackProps & { | ||
LoadingFallback: JSX.Element; | ||
keys?: ErrorBoundaryCommonProps["keys"]; | ||
}; | ||
|
||
const AsyncBoundary = (props: PropsWithChildren<Props>) => { | ||
const { | ||
LoadingFallback, | ||
errorFallbackRender, | ||
ErrorFallback, | ||
children, | ||
keys, | ||
} = props; | ||
const { reset } = useQueryErrorResetBoundary(); | ||
const resetHandler = useCallback(() => { | ||
reset(); | ||
}, [reset]); | ||
|
||
if (typeof errorFallbackRender === "function") | ||
return ( | ||
<ErrorBoundary | ||
reset={resetHandler} | ||
errorFallbackRender={errorFallbackRender} | ||
keys={keys} | ||
> | ||
<Suspense fallback={LoadingFallback}>{children}</Suspense> | ||
</ErrorBoundary> | ||
); | ||
if (ErrorFallback) | ||
return ( | ||
<ErrorBoundary | ||
reset={resetHandler} | ||
ErrorFallback={ErrorFallback} | ||
keys={keys} | ||
> | ||
<Suspense fallback={LoadingFallback}>{children}</Suspense> | ||
</ErrorBoundary> | ||
); | ||
return <Suspense fallback={LoadingFallback}>{children}</Suspense>; | ||
}; | ||
|
||
export default AsyncBoundary; |
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,95 @@ | ||
import React from 'react'; | ||
|
||
export type BasicErrorFallbackRenderProps = { | ||
reset: () => void; | ||
error: Error | null; | ||
}; | ||
|
||
export type ErrorBoundaryCommonProps = React.PropsWithChildren<{ | ||
reset?: () => void; | ||
keys?: Array<unknown>; | ||
}>; | ||
|
||
declare function ErrorFallbackRender({ | ||
reset, | ||
error, | ||
}: BasicErrorFallbackRenderProps): React.ReactNode; | ||
|
||
export type ErrorBoundaryWithErrorFallbackRender = { | ||
errorFallbackRender: typeof ErrorFallbackRender; | ||
ErrorFallback?: never; | ||
}; | ||
|
||
export type ErrorBoundaryWithErrorFallback = { | ||
errorFallbackRender?: never; | ||
ErrorFallback: React.ReactNode; | ||
}; | ||
|
||
type ErrorBoundaryProps = ErrorBoundaryCommonProps & | ||
(ErrorBoundaryWithErrorFallbackRender | ErrorBoundaryWithErrorFallback); | ||
|
||
type State = { | ||
hasError: boolean; | ||
error: Error | null; | ||
}; | ||
const initialState = { hasError: false, error: null }; | ||
|
||
const changedArray = ( | ||
prevArray: Array<unknown> = [], | ||
nextArray: Array<unknown> = [] | ||
) => { | ||
return ( | ||
prevArray.length !== nextArray.length || | ||
prevArray.some((item, index) => { | ||
return !Object.is(item, nextArray[index]); | ||
}) | ||
); | ||
}; | ||
|
||
export default class ErrorBoundary extends React.Component< | ||
ErrorBoundaryProps, | ||
State | ||
> { | ||
constructor(props: ErrorBoundaryProps) { | ||
super(props); | ||
this.state = initialState; | ||
} | ||
|
||
static getDerivedStateFromError(error: Error) { | ||
return { hasError: true, error }; | ||
} | ||
|
||
componentDidUpdate(prevProps: ErrorBoundaryProps, prevState: State) { | ||
const { error } = this.state; | ||
const { keys } = this.props; | ||
|
||
if ( | ||
error !== null && | ||
prevState.error !== null && | ||
changedArray(prevProps.keys, keys) | ||
) { | ||
this.resetBoundary(); | ||
} | ||
} | ||
|
||
resetBoundary = () => { | ||
const { reset } = this.props; | ||
reset?.(); | ||
this.setState(initialState); | ||
}; | ||
|
||
render() { | ||
const { hasError, error } = this.state; | ||
const { children, errorFallbackRender, ErrorFallback } = this.props; | ||
|
||
if (!hasError) return children; | ||
|
||
if (typeof errorFallbackRender === 'function') { | ||
return errorFallbackRender({ | ||
reset: this.resetBoundary, | ||
error, | ||
}); | ||
} | ||
if (ErrorFallback) return ErrorFallback; | ||
} | ||
} |
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,55 @@ | ||
import { PropsWithChildren, Suspense, useCallback } from "react"; | ||
|
||
import { useQueryErrorResetBoundary } from "@tanstack/react-query"; | ||
import ErrorBoundary, { | ||
ErrorBoundaryCommonProps, | ||
ErrorBoundaryWithErrorFallback, | ||
ErrorBoundaryWithErrorFallbackRender, | ||
} from "./error-boundary"; | ||
|
||
type ErrorBoundaryFallbackProps = | ||
| ErrorBoundaryWithErrorFallback | ||
| ErrorBoundaryWithErrorFallbackRender; | ||
|
||
type Props = ErrorBoundaryFallbackProps & { | ||
LoadingFallback: JSX.Element; | ||
keys?: ErrorBoundaryCommonProps["keys"]; | ||
}; | ||
|
||
const AsyncBoundary = (props: PropsWithChildren<Props>) => { | ||
const { | ||
LoadingFallback, | ||
errorFallbackRender, | ||
ErrorFallback, | ||
children, | ||
keys, | ||
} = props; | ||
const { reset } = useQueryErrorResetBoundary(); | ||
const resetHandler = useCallback(() => { | ||
reset(); | ||
}, [reset]); | ||
|
||
if (typeof errorFallbackRender === "function") | ||
return ( | ||
<ErrorBoundary | ||
reset={resetHandler} | ||
errorFallbackRender={errorFallbackRender} | ||
keys={keys} | ||
> | ||
<Suspense fallback={LoadingFallback}>{children}</Suspense> | ||
</ErrorBoundary> | ||
); | ||
if (ErrorFallback) | ||
return ( | ||
<ErrorBoundary | ||
reset={resetHandler} | ||
ErrorFallback={ErrorFallback} | ||
keys={keys} | ||
> | ||
<Suspense fallback={LoadingFallback}>{children}</Suspense> | ||
</ErrorBoundary> | ||
); | ||
return <Suspense fallback={LoadingFallback}>{children}</Suspense>; | ||
}; | ||
|
||
export default AsyncBoundary; |
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,95 @@ | ||
import React from 'react'; | ||
|
||
export type BasicErrorFallbackRenderProps = { | ||
reset: () => void; | ||
error: Error | null; | ||
}; | ||
|
||
export type ErrorBoundaryCommonProps = React.PropsWithChildren<{ | ||
reset?: () => void; | ||
keys?: Array<unknown>; | ||
}>; | ||
|
||
declare function ErrorFallbackRender({ | ||
reset, | ||
error, | ||
}: BasicErrorFallbackRenderProps): React.ReactNode; | ||
|
||
export type ErrorBoundaryWithErrorFallbackRender = { | ||
errorFallbackRender: typeof ErrorFallbackRender; | ||
ErrorFallback?: never; | ||
}; | ||
|
||
export type ErrorBoundaryWithErrorFallback = { | ||
errorFallbackRender?: never; | ||
ErrorFallback: React.ReactNode; | ||
}; | ||
|
||
type ErrorBoundaryProps = ErrorBoundaryCommonProps & | ||
(ErrorBoundaryWithErrorFallbackRender | ErrorBoundaryWithErrorFallback); | ||
|
||
type State = { | ||
hasError: boolean; | ||
error: Error | null; | ||
}; | ||
const initialState = { hasError: false, error: null }; | ||
|
||
const changedArray = ( | ||
prevArray: Array<unknown> = [], | ||
nextArray: Array<unknown> = [] | ||
) => { | ||
return ( | ||
prevArray.length !== nextArray.length || | ||
prevArray.some((item, index) => { | ||
return !Object.is(item, nextArray[index]); | ||
}) | ||
); | ||
}; | ||
|
||
export default class ErrorBoundary extends React.Component< | ||
ErrorBoundaryProps, | ||
State | ||
> { | ||
constructor(props: ErrorBoundaryProps) { | ||
super(props); | ||
this.state = initialState; | ||
} | ||
|
||
static getDerivedStateFromError(error: Error) { | ||
return { hasError: true, error }; | ||
} | ||
|
||
componentDidUpdate(prevProps: ErrorBoundaryProps, prevState: State) { | ||
const { error } = this.state; | ||
const { keys } = this.props; | ||
|
||
if ( | ||
error !== null && | ||
prevState.error !== null && | ||
changedArray(prevProps.keys, keys) | ||
) { | ||
this.resetBoundary(); | ||
} | ||
} | ||
|
||
resetBoundary = () => { | ||
const { reset } = this.props; | ||
reset?.(); | ||
this.setState(initialState); | ||
}; | ||
|
||
render() { | ||
const { hasError, error } = this.state; | ||
const { children, errorFallbackRender, ErrorFallback } = this.props; | ||
|
||
if (!hasError) return children; | ||
|
||
if (typeof errorFallbackRender === 'function') { | ||
return errorFallbackRender({ | ||
reset: this.resetBoundary, | ||
error, | ||
}); | ||
} | ||
if (ErrorFallback) return ErrorFallback; | ||
} | ||
} |