Skip to content

Commit

Permalink
feat: error-boundary, async-boundary 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
kingyong9169 committed Jan 30, 2024
1 parent a694a64 commit bc42196
Show file tree
Hide file tree
Showing 4 changed files with 300 additions and 0 deletions.
55 changes: 55 additions & 0 deletions apps/back-office/src/components/async-boundary.tsx
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;
95 changes: 95 additions & 0 deletions apps/back-office/src/components/error-boundary.tsx
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;
}
}
55 changes: 55 additions & 0 deletions apps/web/src/components/async-boundary.tsx
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;
95 changes: 95 additions & 0 deletions apps/web/src/components/error-boundary.tsx
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;
}
}

0 comments on commit bc42196

Please sign in to comment.