๐ [React] react-query์ ์ ์ธ์ ์ธ ์๋ฌ ์ฒ๋ฆฌ
ํ์ฌ์์ ๊ฐ๋ฐ ์์ ์๋ฌ ์ฒ๋ฆฌ๊ฐ ๊ณ ๋ํ๋์ด ์์ง ์์, ์๋ฌ ๋ฐ์ ์ ์๋ฌ๊ฐ ํ์ด์ง ์ ์ญ์ผ๋ก ํผ์ ธ๋ฒ๋ ค ์ข์ง ์์ ๊ฒฝํ์ ํ์๋ค.
๋ํ ๋ค๋ค ๋ง์ด ์ ๊ฒฝ์ฐ๊ณ ์์ง ์๋ ๋ถ๋ถ์ด๋ค ๋ณด๋ ์ฒ๋ฆฌ๋ ์ ๊ฐ๊ฐ์ด๊ณ ์ด๋ค ๊ณณ์ ์๋ฌ ์ฒ๋ฆฌ์กฐ์ฐจ ๋์ด ์์ง ์์ ๊ณณ์ด ๋ง์๊ธฐ์, ์ด๋ฅผ ํด๊ฒฐํด๋ณด๊ณ ์ถ์๋ค.
๊ฐ์
React Query Error Handling ์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ react-query์ ํจ๊ป ์ ์ฉํ ์ ์๋ ์๋ฌ ์ฒ๋ฆฌ์ ๋ํ ๊ตฌ์กฐ๋ฅผ ์ก์๋ณด๊ณ ์ ํ๋ค.
- ๋์๊ฐ ์๋ฌ๋ฅผ ํ ๊ณณ์ผ๋ก ๋ชจ์ ๋ก๊น ํ๊ณ ์ข ๋ฅ๋ฅผ ๋๋๊ณ ์๋ฆผ์ ์ค ์ ์๋ ๊ตฌ์กฐ๊ฐ ํ์
๋ชฉํ
- ์ฌ์ฉ์์๊ฒ ์์ด ์๋ฌ ๋ฐ์ ์ ์๋ฌ์ ์ข
๋ฅ์ ๋ฐ๋ผ ์ธ๋ถํ ๋ ์๋ด ์ ๊ณต
- ์์ํ ์๋ฌ(CustomError ๋ฑ์ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌํ๊ณ ์๋ ์๋ฌ)์ ์์ํ์ง ๋ชป ํ ์๋ฌ
- ErrorBoundary๋ฅผ ์กฐ๊ธ ๋ ์์ ๋จ์๋ก ๊ฐ์ธ๋๋ก ํด์, ์ฌ์ฉํ ์ ์๋ UI๋ ์ฌ์ฉํ ์ ์๋๋ก ํจ
TL;DR
ํฌ๊ฒ ๋ ๊ฐ์ง๋ก ์๋ฌ ์ํฉ์ ๊ตฌ๋ถํ๊ณ ๊ทธ์ ๋ฐ๋ฅธ ์ ์ ํ ํผ๋๋ฐฑ์ ์ ๊ณตํ๊ณ ์ ํ๋ค.
1. ์์ํ ์๋ฌ์ด๊ณ , ์ํฉ์ ๋ํ ํผ๋๋ฐฑ๊ณผ ์๋ก๊ณ ์นจ UI๋ฅผ ์ ๊ณตํ๊ณ ์ถ์ด์!
๋คํธ์ํฌ ์๋ฌ์ ๊ฐ์ด ์ผ์์ ์ธ ์ฅ์ ์ํฉ์ด๊ฑฐ๋ ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ์๋ต์ ์ ์ถํด ์ ์์ ์ธ ์๋ต์ ๋ฐ์ ์ ์๋ ๊ฒฝ์ฐ
**ErrorResetBoundary**
- react-query์ ํจ๊ป ์ฌ์ฉํฉ๋๋ค.
- ๋ด๋ถ์ ์ผ๋ก react-error-boundary๋ฅผ wrappingํ ์์ฒด ErrorBoundary๋ฅผ ์ฌ์ฉ์ค์ ๋๋ค.
- onError์์๋ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ์์๊ฒ ์ ๊ณตํ ์ ์๋ ์๋ต์ ํ ์คํธ๋ก ๋์๋๋ค.
- ํด๋น ์๋ฌ ๋ฐ์ด๋๋ฆฌ์์ ์ฒ๋ฆฌํ ์ ์๋ ์๋ฌ์ผ ๊ฒฝ์ฐ(์์์น ๋ชปํ ์๋ฌ) ์์ ์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ก ์๋ฌ๋ฅผ ๋์ง๋๋ค.
import {
QueryErrorResetBoundary,
useQueryErrorResetBoundary,
} from "react-query";
function ErrorResetBoundary({
children,
fallbackRender,
onError,
onReset,
...props
}) {
const { reset } = useQueryErrorResetBoundary();
const handleError = (error) => {
if (!isExpectedError(error)) {
throw error;
}
if (!onError) {
toast(`์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค. ${error.response.data.err.msg}`, {
type: toast.TYPE.ERROR,
});
} else {
onError(error);
}
};
const handleReset = () => {
if (!onReset) {
reset(); // ์บ์ฑ๋์ด ์๋ ์๋ฌ๋ฅผ ์ด๊ธฐํ
} else {
onReset(); // ์ด ๊ฒฝ์ฐ์๋ useQueryErrorResetBoundary().reset์ ์ํํด์ผ ํจ
}
};
return (
<QueryErrorResetBoundary>
<ErrorBoundary
onError={handleError}
onReset={handleReset}
fallbackRender={fallbackRender ?? ErrorRetryFallback}
{...props}
>
{children}
</ErrorBoundary>
</QueryErrorResetBoundary>
);
}
Default Fallback
function ErrorRetryFallback({ resetErrorBoundary }) {
// react-error-boundary์์ ์๋์ผ๋ก ๋ฃ์ด์ฃผ๋ resetErrorBoundary์ ํตํด ์๋ฌ ๋ฆฌ์
๊ฐ๋ฅ
return <ErrorBox onClick={resetErrorBoundary} />;
}
2. ์์ํ์ง ๋ชป ํ ์๋ฌ์ด๊ณ , ํด๋น ์๋ฌ๋ ์ฌ์ฉ์๊ฐ ํด๊ฒฐํ ์ ์์ด์!
์ฌ์ฉ์์๊ฒ ๋ฌธ์ํ ์ ์๋ UI๋ฅผ ์ ๊ณตํด์ผ ํ๊ณ , ์ด ๋ ๋ฐ์ํ ์๋ฌ๋ ๋ชจ๋ํฐ๋ง ๋์ด์ผ ํ๋ ๊ฒฝ์ฐ
์ฑ ์ต์๋จ์ ๊ฐ์ธ๋ ์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ ์ด ๊ฒฝ์ฐ์ ํด๋น๋จ
**ErrorBoundary**
- react-error-boundary๋ฅผ wrappingํ ์ปดํฌ๋ํธ
- onError์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์์์น ๋ชปํ ์๋ฌ๋ฅผ console.error๋ก ์ฐ์ด ๋ก๊ทธ๋ฅผ ๋จ๊ธธ ์ ์๋ค.
- react-query์ ํจ๊ป ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์๋์์ ์ค๋ช
ํ๋
Default Fallback
์ ํตํด ์ ์ญ์ ์ฒ๋ฆฌ๋ฅผ ์ถ๊ฐํ ์ ์๋ค.
function ErrorBoundary({
children,
fallbackRender,
fallback,
onError,
onReset,
...props
}) {
return (
/**
* fallback UI props์ ์ฐ์ ์์
* 1. fallback={<CustomFallback />}
* 2. fallbackRender={() => <CustomFallback />}
* 3. FallbackComponent={CustomFallback} // ๊ตฌ์กฐ๋ถํด ํ ๋น์ด ๋์ด ์์ง ์์
*
* ex1) <ErrorBoundary />
* : defaultProps๋ก ์ง์ ๋์ด ์๋ <ErrorFallback /> ๋ ๋๋ง (fallbackRender)
*
* ex2) <ErrorBoundary fallbackRender={() => <MyFallback />} />
* : <MyFallback /> ๋ ๋๋ง
*
* ex3) <ErrorBoundary fallback={<MyFallback />} />
* : defaultProps๋ก fallbackRender๊ฐ ์ง์ ๋๊ธด ํ์ง๋ง, fallback์ ์ฐ์ ์์๊ฐ ๋์ผ๋ฏ๋ก <MyFallback /> ๋ ๋๋ง
*/
<ReactErrorBoundary
fallbackRender={fallbackRender}
fallback={fallback}
onError={onError}
onReset={onReset}
{...props}
>
{children}
</ReactErrorBoundary>
);
}
Default Fallback
- ํ์ด์ง ์ ์ฒด๋ฅผ ๋ฎ๋ fallback
function ErrorFallback({ resetErrorBoundary }) {
const handleClick = () => {
resetErrorBoundary();
history.push(-1);
};
return (
<div className={styles.errorWrapper}>
<div className={styles.warningBox}>
<WarningIcon width="28" height="28" />
</div>
<div className="text-gray-600">
์์์น ๋ชปํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
<br /> ์ ์ ํ ๋ค์ ์๋ํด ์ฃผ์ธ์.
</div>
<Button onClick={handleClick} color="basic" outline>
<BackPageIcon />
๋ค๋ก๊ฐ๊ธฐ
</Button>
</div>
);
}
react-query๋ฅผ ํตํด ์์ํ์ง ๋ชปํ ์๋ฌ์ ๋ํด ์ ์ญ์ ์ฒ๋ฆฌ๋ฅผ ํ ๊ฒฝ์ฐ
- ์ฑ๋จ์์ queryClient๋ฅผ ์ ์ธํ ๋ ๊ธ๋ก๋ฒ ์ฝ๋ฐฑ์ ๋ฑ๋กํ ์ ์๋ค.
- ์ด๋ฅผ ํตํด fallback UI๋ง ์ ๊ณตํ ๋ฟ ์๋๋ผ ์ถ๊ฐ์ ์ผ๋ก
์๋ฒ์์ ์์์น ๋ชปํ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.
ํ ์คํธ๋ฅผ ์ผ๊ด์ ์ผ๋ก ์ ๊ณตํ ์ ์๋ค.
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (err) => {
handleUnExpectedError(err);
},
}),
mutationCache: new MutationCache({
onError: (err) => {
handleUnExpectedError(err);
},
}),
...
});
๊ธฐ์กด ๋ฐฉ์์ ๋ฌธ์ ์ ๋ถ์ ๋ฐ ํด๊ฒฐ ๋ฐฉ์ ์ ์
AS-IS
- ์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ฅผ ์ ์ ํ ๋จ์๋ก ๊ฐ์ธ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์, ํ ์ปดํฌ๋ํธ์์ ์์์น ๋ชปํ ์๋ฌ ๋ฐ์ ์ ์๋ฌ๊ฐ ์ ์ญ์ผ๋ก ํผ์ ธ ํ๋ฉด ์ ์ฒด๋ฅผ ๋ฎ๋ fallback ๋ ๋๋ง
- ๋ฐ์ํ ์๋ฌ์ ๋ํด ์ฒ๋ฆฌ ๋ฐฉ์์ด ์ฐ์ฌ๋์ด ์์
- query, mutation์ onError ์ฝ๋ฐฑ์ ํตํ ์ง์ญ์ ์ฒ๋ฆฌ
- ํน์ ์ปดํฌ๋ํธ์์ isError๋ฅผ ํ์ธํด ๋ช ๋ น์ ์ผ๋ก ์ฒ๋ฆฌ
- ์ด๋ค ์๋ฌ ์ฒ๋ฆฌ๋ ๋์ด์์ง ์์ ์๋ฌ ๋ฐ์ ์ ์ ์ญ์ผ๋ก ์๋ฌ๊ฐ ํผ์ง
TO-BE
์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ฅผ ์ ์ ํ ๋จ์๋ก ๊ฐ์ธ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์, ํ ์ปดํฌ๋ํธ์์ ์์์น ๋ชปํ ์๋ฌ ๋ฐ์ ์ ์๋ฌ๊ฐ ์ ์ญ์ผ๋ก ํผ์ ธ ํ๋ฉด ์ ์ฒด๋ฅผ ๋ฎ๋ fallback ๋ ๋๋ง
TeachConsoleApp ๊ธฐ์ค์ผ๋ก PageLayout ์ปดํฌ๋ํธ ๋ด๋ถ๋ฅผ ์ ์ ํ ๋จ์ ์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ก ๊ฐ์ธ ์์์น ๋ชปํ ์๋ฌ๊ฐ ๋ฐ์ํ์ ๊ฒฝ์ฐ, ์ฌ์ฉํ ์ ์๋ ์ปดํฌ๋ํธ๋ ์ฌ์ฉ ๊ฐ๋ฅํ๋๋ก ๋ณ๊ฒฝ
- ์๋ฌ๊ฐ ๋ฐ์ํ์ ๊ฒฝ์ฐ ์๋ฌ๊ฐ ๋ฐ์ํ ์ปดํฌ๋ํธ์ ์๋ฌ๋ฅผ ๊ฐ๋
- retry UI๋ฅผ ์ ๊ณตํ๋ ErrorResetBoundary ์ฌ์ฉ ์์, ํด๋น ์๋ฌ ๋ฐ์ด๋๋ฆฌ์์ ์ฒ๋ฆฌํ ์ ์๋ ์๋ฌ์ ๊ฒฝ์ฐ ์๋ฌ๋ฅผ ๋์ ธ ์์ ์๋ฌ๋ฐ์ด๋๋ฆฌ์์ ์ฒ๋ฆฌํ ์ ์๋๋ก ํจ
๋ฐ์ํ ์๋ฌ์ ๋ํด ์ฒ๋ฆฌ ๋ฐฉ์์ด ์ฐ์ฌ๋์ด ์์
ErrorBoundary์ onError๋ฅผ ํตํด ์๋ฌ ์ฒ๋ฆฌ ๋ฐฉ์์ ์ผ์ํํ๊ณ , ์ถ๊ฐ์ ์ผ๋ก react-query์ queryClient ์ ์ธ ์ queryCache, mutationCache์ ๊ธ๋ก๋ฒ ์ฝ๋ฐฑ์ ๋ฑ๋กํด ์ ์ญ์ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅ
- isError๋ฅผ ํ์ธํด ์ปดํฌ๋ํธ์์ ๋ช ๋ น์ ์ผ๋ก ์๋ฌ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ๋ ๋ชจ๋ ์ ๊ฑฐ ๊ฐ๋ฅ
- query์ onError ์ฝ๋ฐฑ ์ฌ์ฉ ์ ์๋ฌ ์ฒ๋ฆฌ์ ๋ํ ์์ง๋๊ฐ ๋ฎ์์ง๋ ๊ฒ์ ErrorBoundary์ onError๋ฅผ ํตํด ํด๊ฒฐ
TkDodo๊ฐ ์ถ์ฒํ๋ ๋ฐฉ์์ ๋ํด ํ ๊ตฌ์กฐ ๋ถ์
์ํ๋ ๋๋ก ํผํฉํ๊ณ ์ผ์น์ํฌ ์ ์์ต๋๋ค. ์ ๊ฐ ๊ฐ์ธ์ ์ผ๋ก ํ๊ณ ์ถ์ ์ผ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ฌ๊ฒ์(์ค๋๋ UI๋ฅผ ๊ทธ๋๋ก ์ ์งํ๊ธฐ ์ํด)์ ๋ํ ์ค๋ฅ ํ ์คํธ๋ฅผ ํ์ํ๊ณ ๋ค๋ฅธ ๋ชจ๋ ์์ ์ ๋ก์ปฌ ๋๋ Error Boundaries๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ ๋๋ค.
๊ธ ์ด๋ฐ์ ์๊ฐํ TkDodo์ ๊ฒ์๋ฌผ์ ํ ๋๋ก ํ ๊ตฌ์กฐ๋ฅผ ์ดํด๋ณด์๊ณ ์ด๋ค ๊ฒ์ด ๋ฌธ์ ์ธ์ง, ์ด๋ค ๋ฐฉ์์ผ๋ก ํด์ํ ์ ์๋์ง ๊ณ ๋ฏผํ๋ฉฐ UX์ DX๋ฅผ ๋ชจ๋ ๊ฐ์ ํ ์ ์๋ ๋ฐฉ์์ ๊ณ ์ํ ์ ์์๋ค.
์ฌํด ์ด ErrorBoundary์ Suspense๋ฅผ ๋์ ํ ๊ฒ์ ์ด์ด, ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ ํ ์ ์๋ ๋ฐฉ์์ ๊ณ ๋ฏผํด ๋ณผ ์ ์์ด์ ์๋ฏธ์๋ ์๊ฐ์ด์๋ค.
์๋กญ๊ฒ ์๊ฒ๋ ๊ฒ๋ค
- isError ํ๋๊ทธ
- ๋ฐฑ๊ทธ๋ผ์ด๋(re-fetch) ์ค๋ฅ๋ฅผ ์ ํด๊ฒฐํ์ง ๋ชปํจ. ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํจ์น๊ฐ ์คํจํ์ ๊ฒฝ์ฐ ์ฟผ๋ฆฌ๋ ์๋ฌ์ ์ด์ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ๊ฐ์ง๊ณ ์์. ์๋ฌ๊ฐ ์๋ค๊ณ ๋ฐ๋ก ์๋ฌ ์นด๋๋ก ๋ณด์ฌ์ฃผ๋ ๊ฒ ๋ณด๋ค ๋ ๋ค์ํ ์ฒ๋ฆฌ๋ฅผ ์๊ฐํด๋ณผ ์ ์์
- useQuery์ onError ์ฝ๋ฐฑ
- ํ๋์ ์ฟผ๋ฆฌ์ ์ฌ๋ฌ ์ต์ ๋ฒ๊ฐ ๋ถ์ด์์ ๊ฒฝ์ฐ onError ์ฝ๋ฐฑ๋ ์ฌ๋ฌ๋ฒ ํธ์ถ๋จ
react-error-boundary
fallback UI props์ ์ฐ์ ์์- ๊ธฐ์กด
ErrorBoundary
๋ฅผ ๊ฐ์ ํ๋ฉฐ fallback์ ์ฐ์ ์์์ ๋ํด ์๊ฒ๋์๋ค. ์ด์ ์๋ ์ด๋ฌํ ์ฐ์ ์์๋ฅผ ์ ์ง ๋ชปํด์ ํน์ props๋ง ๋ฐ๋๋ก ๊ฐ์ ํ์๋๋ฐ, ํจ๊ป ์ผํ๋ ๋๋ฃ๊ฐ ์ง์ ์ฝ๋๋ฅผ ๊น๋ณด๋ฉฐ ์ฐ์ ์์๊ฐ ์์์์ ์๋ ค์ฃผ์๋ค.
- ๊ธฐ์กด
์คํ์ ๋ก์ค ํฌ. @์ ๋ก @๋ฃจํค
- ๊ด๋ จํด์ ์ข์ ์๊ฒฌ ์ฃผ์ ์ ๋ ๋์ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์ด ๋ผ ์ ์์์ต๋๋ค. ๊ฐ์ฌํด์ :>
์ฐธ๊ณ ํ๋ ๊ธ ๋ค
- ํด๋ผ์ด์ธํธ์ ์ฌ์ฉ์ ์ค์ฌ ์๋ฌ ์ฒ๋ฆฌ
- React Query์ ํจ๊ป Concurrent UI Pattern์ ๋์ ํ๋ ๋ฐฉ๋ฒ
# ์นดํ ๊ณ ๋ฆฌ
- BOJ 36
- Algorithm 12
- CodingTest 11
- Web 9
- Javascript 8
- Vue 7
- React 7
- DBProject 4
- Python 3
- Tech-interview 3
- Express 3
- Next 3
- Github 2
- Django 2
- C 1
- C++ 1
- WebGame 1