[React] SWRで一つ前のデータを取得するhook

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)
  );
  ...

コメントを残す

メールアドレスが公開されることはありません。