SWRのエラーを共通化する方法についてまとめます。
Reactプロジェクトがあるのを前提とします。
目次
SWRConfigでエラー処理の共通化をする方法
SWRConfigでSWRについてのグローバルな設定ができます。
https://swr.vercel.app/ja/docs/global-configuration
その中にあるonErrorでエラー発生時の処理を書くことができます。
以下のようなイメージです。今回はfetcherにkyを使っている前提で書いています。
400ならページ上部にスナックバー表示、それ以外ならErrorFallbackに任せるみたいな流れにするとSWRのhookを使った時にほとんどエラー処理する必要がなくなります。
import { HTTPError } from "ky";
import { SWRConfig } from "swr";
const App = () => {
<SWRConfig
value={{
onError: async (err) => {
if (err instanceof HTTPError) {
const res = await err.response.clone();
if (res.status === 400) {
// snackbarでエラー表示など
return;
}
}
// ErrorFallbackなどその他のエラー処理
handleError(err);
},
}}
>
...
サンプルコード
では、実際にSWRConfigを使ったエラー共通化のサンプルコードを書いていきます。
400エラーが起きたらsnackbarでエラー表示、その他のエラーはErrorBoundary/ErrorFallbackで共通処理するサンプルです。
ちなみに完成したコードはこちらに上げています。
https://github.com/pei223/ReactSWRErrorHandling
ライブラリインストール
まずは必要なライブラリのインストール。kyはAPIアクセス、notistackはスナックバー表示、react-error-boundaryはErrorBoundary関連のライブラリです。
npm i swr ky notistack react-error-boundary
SWRConfigのエラー処理の仕組み周り
まずは起点となるApp.tsx。
TestAppの方でSnackbarProvider、ErrorBoundaryの設定をします。これで以下のコンポーネントでスナックバー表示、ErrorBoundaryへの処理の移譲ができます。
次にAppRouter。本来ここにReact-routerのルーティングなど書くのでAppRouterという命名です。
ここでSWRConfigのonErrorでエラー処理を書いています。400エラーならスナックバー表示、それ以外ならErrorFallbackに処理を移譲する感じです。
import React from "react";
import { ErrorBoundary, useErrorHandler } from "react-error-boundary";
import { SnackbarProvider, useSnackbar } from "notistack";
import { SWRConfig } from "swr";
import TestComponent from "./components/TestComponent";
import TestErrorFallback from "./TestErrorFallback";
import { HTTPError } from "ky";
const AppRouter = () => {
const handleError = useErrorHandler();
const { enqueueSnackbar } = useSnackbar();
return (
<SWRConfig
value={{
onError: async (err) => {
if (err instanceof HTTPError) {
const res = await err.response.clone();
if (res.status === 400) {
enqueueSnackbar(`BadRequest: ${err.message}`, {
variant: "error",
});
return;
}
}
handleError(err);
},
}}
>
{/** ここにReact routerの設定など */}
<TestComponent />
</SWRConfig>
);
};
function TestApp() {
return (
<SnackbarProvider
maxSnack={5}
anchorOrigin={{ horizontal: "center", vertical: "top" }}
autoHideDuration={3000}
>
<ErrorBoundary FallbackComponent={TestErrorFallback}>
<AppRouter />
</ErrorBoundary>
</SnackbarProvider>
);
}
export default TestApp;
次はErrorFallback。エラーメッセージだけ表示してますが、ちゃんとやるなら401は再ログイン促したりエラーレスポンス取得してその内容表示とかしたほうがいいです。
import React from "react";
import { FallbackProps } from "react-error-boundary";
const TestErrorFallback: React.FC<FallbackProps> = ({
error,
resetErrorBoundary,
}) => {
return (
<div>
<h1>Error</h1>
<p>message: {error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
};
export default TestErrorFallback;
最後にコンポーネント実装
最後にそれらの仕組みが整った上でコンポーネントの実装。
useSWRでデータ取得処理書くだけでエラーが発生したらSWRConfig/ErrorFallbackがやってくれます。
import React from "react";
import ky from "ky";
import useSWR from "swr";
type TodoList = {
items: Todo[];
total: number;
};
type Todo = {
id: string;
title: string;
done: boolean;
};
const fetcher = (url: string): Promise<TodoList> => {
return ky.get(url).json();
};
const TestComponent = () => {
const { data, error } = useSWR<TodoList>(["http://localhost:8080/todos"], fetcher);
const loading = !error && !data;
return (
<div>
{loading && <div>Loading...</div>}
{data &&
data.items.map((todo) => (
<div
key={todo.id}
style={{
borderTop: "1px solid gray",
}}
>
<p>id: {todo.id}</p>
<p>title: {todo.title}</p>
<p>status: {todo.done ? "finished" : "not finished"}</p>
</div>
))}
</div>
);
};
export default TestComponent;