SWRでは一つ前のデータ・状態を取得する機能がありません。
普通にやるとコンポーネントに余計なstateなどが増えてしまうので、コンポーネント外で共通化する方法についてまとめます。
目次
SWRの一つ前のデータの保持を普通にやろうとするとどうなるか
普通にやろうとすると、一つ前のデータを示すstateを一つ増やして、useRefで一つ前のデータを保持しておいてuseEffect使って更新するみたいな実装になると思います。
一つ前のデータ取得するためにいちいちstate, ref, useEffectが1つずつ増えるのは無駄に感じます。
今回はこれを共通化します。
import React, { useEffect, useRef, useState } 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("http://localhost:8080/todos").json();
};
const TestComponent = () => {
const { data, error } = useSWR<TodoList>(["todos"], fetcher);
const [prevData, setPrevData] = useState<TodoList | undefined>(undefined);
const prevDataRef = useRef<TodoList | undefined>(undefined);
useEffect(() => {
setPrevData(prevDataRef.current);
prevDataRef.current = data;
}, [data]);
...
SWRで一つ前のデータを取得するhook
useSWRの戻り値をラッピングする関数を実装します。
useRefで一つ前のデータを保持しておいて、SWRResponseのdataが変更されたらRefから一つ前のデータを取得してstateを更新する感じです。
Reactのコンポーネント内で使われるならstateなどのReactのhookはコンポーネント外でも実装できます。SWR自体もそんな実装になってます。
import { useEffect, useRef, useState } from "react";
import { SWRResponse } from "swr";
export const withPrevData = <T>(
swrHookRes: SWRResponse<T>
): SWRResponse<T> & { prevData: T | undefined } => {
const [prevData, setPrevData] = useState<T | undefined>(undefined);
const prevDataRef = useRef<T | undefined>(undefined);
useEffect(() => {
setPrevData(prevDataRef.current);
prevDataRef.current = swrHookRes.data;
}, [swrHookRes.data]);
return { ...swrHookRes, prevData };
};
withPrevDataの使い方
withPrevData(useSWR(…))といった感じでuseSWRをラッピングして使います。
dataが更新された時にprevDataは一つ前のデータに更新されます。
普通にやる場合と比べるとコンポーネント側はだいぶシンプルになったと思います。
import React from "react";
import ky from "ky";
import useSWR from "swr";
import { withPrevData } from "./swr-helper";
type TodoList = {
items: Todo[];
total: number;
};
type Todo = {
id: string;
title: string;
done: boolean;
};
const fetcher = (url: string): Promise<TodoList> => {
return ky.get("http://localhost:8080/todos").json();
};
const TestComponent = () => {
const { data, error, prevData } = withPrevData(
useSWR<TodoList>(["todos"], fetcher)
);
...