React QueryはReactのData Fetchingライブラリでサーバからデータを取得する際に利用することができます。React Queryのドキュメントには”React Query makes fetching, caching, synchronizing and updating server state in React App a breeze”で記載されています。説明からサーバからデータを取得したり取得したデータをキャッシュしたりするためのライブラリであることはわかりますが実際にどのように動作するのか知りたいという人も多いかと思います。本文書ではReact環境を構築しuseQueryが実際にどのような機能を持っているのかuseState, useEffectの使い方を知っているだけのReactのビギナーレベルの人でも理解できようにできるだけ簡単な例を利用して説明しています。

breezeという単語は日本語では微風という意味がありますがここでは簡単という意味で使われています。

Reactのプロジェクトの作成

useQueryの動作確認を行うためにReactのプロジェクトの作成を行います。npx crearte-react-appコマンドを実行します。プロジェクトの名前には任意の名前をつけてください。


 % npx create-react-app react-userquery-test

Reactプロジェクトが作成後、通常の方法でのデータ取得の方法確認し、その後useQueryを利用したデータの取得方法を確認していきます。

useQueryの利用しない場合

Userコンポーネントの作成

srcフォルダにcomponentsフォルダを作成しその下にUser.jsファイルを作成してください。

fetch関数を利用してデータを取得しますが、そのためには外部にデータリソースが必要となります。ここではJSONPlaceholderを利用させてもらいます。下記のURLにアクセスすると10名分のユーザ情報を取得することができます。ブラウザからも取得が可能なのでどのようなデータが取得できるか確認してみてください。


https://jsonplaceholder.typicode.com/users

User.jsファイルではReact HookのuseEffectを利用してマウント時にfetch関数を実行しJSONPlaceholiderからユーザデータを取得し、取得後はReact HookのuseStateを利用して取得したデータを保存します。その後はmap関数で展開を行うユーザ一覧をブラウザ上に表示させています。


import { useState, useEffect } from 'react';

const fetchUsers = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/users');
  return res.json();
};

function User() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetchUsers().then((data) => {
      setUsers(data);
    });
  }, []);

  return (
    <div>
      <h2>ユーザ一覧</h2>
      <div>
        {users.map((user) => (
          <div key={user.id}>{user.name}</div>
        ))}
      </div>
    </div>
  );
}

export default User;

App.jsファイルの更新

Userコンポーネントの作成が完了したらApp.jsファイルを更新してUserコンポーネントをimportします。


import User from './components/User';

function App() {
  return (
    <div style={{ margin: '2em' }}>
      <h1>ユーザ情報</h1>
      <User />
    </div>
  );
}

export default App;

npm startコマンドを実行するとブラウザ上には10名分のユーザ一覧が表示されます。

ユーザ一覧表示
ユーザ一覧表示

useQueryを利用しない方法でユーザ一覧を表示することができたので次はuseQueryを利用してユーザ一覧を表示させます。

useQueryを利用する場合

useQueryを利用するためにはreact-queryパッケージをインストールする必要があります。


$ npm install react-query

UserコンポーネントではuseQueryを使ってコードで書き換えます。一時的にmap関数の処理は実行しないようにしておきます。この時点で先ほど利用していたReact HookのuseStateとuseEffectを削除しています。


import { useQuery } from 'react-query';

const fetchUsers = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/users');
  return res.json();
};

function User() {
  const result = useQuery('users', fetchUsers);
  console.log(result)
  return (
    <div>
      <h2>ユーザ一覧</h2>
      <div>
        {/* data.map((user) => (
          <div key={user.id}>{user.name}</div>
        ))*/}
      </div>
    </div>
  );
}

export default User;

useQueryではコードにある通り、第一引数に任意の名前のユニークなキー、第二引数にはpromiseを戻す関数を設定します。


const result = useQuery('ユニークキー', 'Promiseを返す関数')

QueryClientの設定

useQueryの動作確認を行う前にApp.jsファイルでQueryClientとQueryClientProviderの設定を行う必要があります。QueryClientはキャッシュ情報とのやりとりに利用されます。


import { QueryClient, QueryClientProvider } from 'react-query';
import User from './components/User';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <div style={{ margin: '2em' }}>
        <h1>ユーザ情報</h1>
        <User />
      </div>
    </QueryClientProvider>
  );
}

export default App;

もしQueryClientとQueryClientProviderの設定を行なっていない場合は以下のエラーメッセージが表示されます。

Error: No QueryClient set, use QueryClientProvider to set one

useQueryの実行結果の確認

useQueryの実行できる環境が整ったのでブラウザでアクセスしてデベロッパーツールのコンソールを確認してください。useQueryの結果はオブジェクトとして取得でき下記のようなメッセージが表示されます。

2つのオブジェクトのプロパティを見るとstatus, isLoading, isSuccessなど値が異なっています。

useQueryの実行結果
useQueryの実行結果

statusがloadingのオブジェクトを開いてみます。オブジェクトは複数のプロパティから構成されていることがわかります。

statusがloadingの中身を確認
statusがloadingの中身を確認

statusがsuccessのオブジェクトを開いてみます。

statusがsuccessの中身を確認
statusがsuccessの中身を確認

2つの内容を確認するとstatusがloadingの時はdataプロパティはundefinedでstatusがsucessの時はdataプロパティにはデータが含まれています。

ユーザの一覧を表示させるためresultの中からdataのみを取得してmap関数で展開させてみましょう。


import { useQuery } from 'react-query';

const fetchUsers = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/users');
  return res.json();
};

function User() {
  const { data } = useQuery('users', fetchUsers);

  return (
    <div>
      <h2>ユーザ一覧</h2>
      <div>
        {data.map((user) => (
          <div key={user.id}>{user.name}</div>
        ))}
      </div>
    </div>
  );
}

export default User;

ブラウザで確認すると”TypeError: Cannot read property ‘map’ of undefined”のエラー画面が表示されます。

先ほどstatusがloadingの場合のdataプロパティがundefinedになっていたことが原因です。undefinedなのでmap関数で展開することができないためにエラーが発生しています。

useQueryではstatus, isLoading, isError, isSucessなどデータ取得に関する状態を取得するためにさまざまなプロパティが準備されています。dataプロパティがundefinedによる問題もisLoadingを利用することで下記のように解決することができます。


import { useQuery } from 'react-query';

const fetchUsers = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/users');
  return res.json();
};

function User() {

  const { data,isLoading } = useQuery('users', fetchUsers);

  if (isLoading) {
    return <span>Loading...</span>;
  }

  return (
    <div>
      <h2>ユーザ一覧</h2>
      <div>
        {data.map((user) => (
          <div key={user.id}>{user.name}</div>
        ))}
      </div>
    </div>
  );
}

export default User;

isLoadingがfalseからtrueになるまでmap関数を実行されないのでエラーは発生せずユーザの一覧を表示することができます。

useQueryによるユーザ一覧を表示
useQueryによるユーザ一覧を表示

ここまででuseQueryを利用した方法でもユーザ一覧を表示させることができました。

エラーに対する処理

エラーによりデータが取得できなかった場合の処理についてはisErrorプロパティを利用することで対応することができます。

エラーを発生させるためにfetchを行う場所は存在しないURL(https://jsonplaceholder.typicode.co)に変更しています。今回はresultの中からdata, isLoadingに加えてisErrorとerrorを利用します。errorにはエラーの情報が含まれています。


import { useQuery } from 'react-query';

const fetchUsers = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.co');
  return res.json();
};

function User() {

  const { data, isLoading, isError, error } = useQuery('users', fetchUsers);

  if (isLoading) {
    return <span>Loading...</span>;
  }

  if (isError) {
    return <span>Error: {error.message}</span>;
  }

  return (
    <div>
      <h2>ユーザ一覧</h2>
      <div>
        {data.map((user) => (
          <div key={user.id}>{user.name}</div>
        ))}
      </div>
    </div>
  );
}

export default User;

ブラウザで確認するとすぐにエラーが発生するわけではなくしばらくの間Loadingの画面が表示されます。

Loadingメッセージが表示
Loadingメッセージが表示

一定時間が経過すると画面にはエラーメッセージが表示されます。

エラーメッセージが表示
エラーメッセージが表示

useQueryを利用することで数行のコードだけでエラーへの対応も行えるようになりました。

エラーの処理については理解できましたがLoadingからエラーメッセージ表示までに一体何が裏側で起こっているのか気になるのでさらに調査を進めます。

エラー時にデベロッパーツールを開いてメッセージを見るとエラーが発生しても複数回GETメソッドが実行されていることがわかります。

コンソールメッセージの確認
コンソールメッセージの確認

ネットワークタブを確認すると3回fetchが行われていることがわかります。

ネットワークタブの確認
ネットワークタブの確認

React Queryのドキュメントを確認するとデータが取得できない場合にデフォルトでは3回のリトライを行うことがわかります。React Queryには自動でリトライ機能が設定されています。またリトライの回数はオプションによって変更することもできます。

リトライのデフォルト値の確認
リトライのデフォルト値の確認

試しにリトライの回数をデフォルトの3から5に変更して動作確認を行います。オプションはuseQueryの第三引数にオブジェクトとして設定することができます。


const { data, isLoading, isError, error } = useQuery('users', fetchUsers, {
  retry: 5,
});

5に変更することでリトライの回数が増えていることが確認できます。

リトライの回数の変更
リトライの回数の変更

メッセージが表示される間隔をみていると回数を重ねるたびに長くなっていることにも気づきます。リトライの間隔についてもドキュメントに記載せれておりデフォルトでは1000msから始まり2倍、さらに2倍と指数関数的に間隔が広くなり最大は30秒に設定されることがわかります。

リトライの間隔
リトライの間隔

リトライを行いたくない場合はretryをfalseに設定してください。即座にエラーメッセージが表示されます。

ウィンドウフォーカスリフェッチ

エラーの動作確認を行っている際に画面に”Error:Failed to fetch”と表示後に再度ブラウザのウィンドウをクリックすると再度Loading画面が表示されデータの取得処理が再実行されます。

これはuseQueryの持つWindow Focus Refetching機能で画面をクリックすると自動でFetchが再実行されます。これはエラーが発生した時に行われるものではなくエラーが発生しない場合でも再フェッチが行われています。Window Focus Refetching機能についても確認していきましょう。

再度正しいUserコンポーネントのURLを正しいURLに直します。


const res = await fetch('https://jsonplaceholder.typicode.com/users');

ブラウザでアクセスするとユーザ一覧が表示されます。

デベロッパーツールのネットワークタブを開いて一度履歴の削除を行なってください。下記の画面の赤丸で囲まれた禁止マークをクリックしてください。

履歴の削除
履歴の削除

その後画面をクリックするとリフェッチ(データの再取得)が自動で行われていることが確認できます。

リフェッチの確認
リフェッチの確認

画面をクリックすればリフェッチが行われるわけではなくそのウィンドウから一度外れなければ画面を何度クリックしてもリフェッチは行われません。一度そのウィンドウから外れ外側をクリックして再度ウィンドウに戻りクリックを行うとリフェッチが行われます。

Window Focus Refetching機能はデフォルトではonになっているのでオプションでoffにすることもできます。


const { data, isLoading, isError, error } = useQuery('users', fetchUsers, {
  refetchOnWindowFocus: false,
});

設定完了後にウィンドウをクリックしてもリフェッチは行われません。

Window Focus Refetching機能によってサーバ上の最新の情報をユーザが意識することなく自動で取得することがわかりました。

キャッシュの動作確認

useQueryの機能の中で重要な機能の一つであるキャッシュについて確認していきます。

サーバからデータを取得するとデフォルトでは5分間(1000*60*5)はキャッシュに保存されます。そのキャッシュ中であれば一度アンマウントしたコンポーネントを再度マウントするとキャッシュからデータを取得するのですぐにブラウザにデータを描写することができます。

キャッシュの動作確認を行うためにApp.jsファイルにshowプロパティを設定しshowをtrue,falseと切り替えることでUserコンポーネントの表示、非表示(マウント、アンマウント)を切り替えます。


import { useState } from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import User from './components/User';

const queryClient = new QueryClient();

function App() {
  const [show, setShow] = useState(true);
  return (
    <QueryClientProvider client={queryClient}>
      <div style={{ margin: '2em' }}>
        <div>
          <button onClick={() => setShow(!show)}>Toggle</button>
        </div>
        <h1>ユーザ情報</h1>
        {show && <User />}
      </div>
    </QueryClientProvider>
  );
}

export default App;

設定後、ブラウザ確認するとtoggleボタンが表示されます。

表示・非表示を切り替えるためのtoggleボタン
表示・非表示を切り替えるためのtoggleボタン

toggleボタンをクリックすることで表示・非表示が行われることを確認してください。statusを見ることでキャッシュからデータを取得しているかどうか確認を行います。キャッシュを利用していない場合はloadingステータスからsuccessステータスに変わります。キャッシュからデータを取得している場合はloadingステータスはなくsuccessステータスが表示されます。


const { data, isLoading, isError, error, status } = useQuery(
  'users',
  fetchUsers,
);
console.log(status);

アクセスした直後ではキャッシュは存在しないので下記のようにloading, successが表示されます。

ページを開いた直後
ページを開いた直後

Toggleボタンを一度クリックして非表示にした後再度Toggleボタンをクリックしてください。キャッシュからデータを取得しているのでsuccessが表示されます。

キャッシュからデータを取得
キャッシュからデータを取得きゃ

キャッシュから取得していてもフェッチは行われなくなるわけではなくバックグランドで実行されデータの取得は行われています。もしサーバ側でデータの更新が行われている場合はまずキャッシュのデータが表示され、サーバからデータの取得後後に変更したデータが反映されます。

デフォルトではキャッシュ期間は5分なので1秒に変更します。設定はcacheTimeで設定を行うことができます。単位はmsです。デフォルトでは1000*60*5に設定されています。


const { data, isLoading, isError, error, status } = useQuery(
  'users',
  fetchUsers,
  {
    cacheTime: 1000,
  }
);
console.log(status);

ブラウザをリロードして、少し時間を置いてからtoggleボタンを押してください。先ほどとは異なりほんの一瞬なので見えないかもしれませんがloading…が表示されます。statusもsuccessのみではなくloadingの後にsuccessが表示されます。

cacheTimeを1秒に設定した場合
cacheTimeを1秒に設定した場合

もしLoaing…をしっかりみたい場合はネットワークタブでSlow 3Gに変更をして動作を確認してください。

3Gネットワークを設定
3Gネットワークを設定

useQueryでcacheTimeの設定の間であればキャッシュに保存されているデータが利用されることがわかりました。

ここまでの動作確認でコンポーネントがマウントした時、ウィンドウがクリックされた時にリフェッチが行われることを確認しました。そのほかにオプションのrefetchIntervalを設定することで設定した間隔でリフェッチを行うことができます。


  const { data, isLoading, isError, error, status } = useQuery(
    'users',
    fetchUsers,
    {
      refetchInterval: 1000,
    }
  );

バックグランドでは1秒後にフェッチが行われることが確認できます。

Devtoolsの設定方法

React Query専用のdevtoolsも準備されているので利用方法を確認します。devtoolsを利用することでuseQueryで設定したユニークキーごとのクエリーに関する情報を見ることができます。

App.jsファイルを開いてReactQueryDevtoolsをimportします。devtoolsを利用するために追加のパッケージ等のインストールは必要ありません。propsのinitailIsOpenではdevtoolをアクセス時にオープンした状態にするかどうか設定を行うことができます。ここではfalseにしています。


import { useState } from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import User from './components/User';
import { ReactQueryDevtools } from 'react-query/devtools';

const queryClient = new QueryClient();

function App() {
  const [show, setShow] = useState(true);
  return (
    <QueryClientProvider client={queryClient}>
      <div style={{ margin: '2em' }}>
        <div>
          <button onClick={() => setShow(!show)}>Toggle</button>
        </div>
        <h1>ユーザ情報</h1>
        {show && <User />}
      </div>
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

export default App;

設定後ブラウザで確認すると左下にReact Queryのロゴが表示されていることがで確認できます。表示する場所もpropsで設定することができます。

devtools設定後の画面
devtools設定後の画面

クリックするとDevtoolが表示されます。左側のパネルには[“users”]が確認できます。これはuseQueryの第一引数に設定したキーの名前でキーを変更するとここの名前も変わります。このキーによって取得したデータが右側のパネルに表示されます(Data Explorer)。

devtoolsが開いた状態
devtoolsが開いた状態

Data Explorerをスクロールすると設定されているcacheTimeなどのパラメータの値も確認することができます。

Queryのパラメータの確認
Queryのパラメータの確認

上部には現在のステータスが表示され、左からfresh、fetching, stale, inactiveと表示されています。ブラウザでアクセスするとfresh→fetching→staleになります。staleはデータ取得から時間が経過し最新の情報ではないということを表しています。デフォルトではstaleの値は0なのでデータを取得するとすぐにstaleのステータスに移動します。staleのデフォルト値を変更することも可能で変更し時間を伸ばすと設定した値の時間だけfreshのステータスで表示されます。freshはデータが最新情報であることを示します。

Toggleボタンを押すとUserコンポーネントは非表示になるので下記のようにinactiveになります。

非表示にするとinactive
非表示にするとinactive

再度Toggleを押すとstaleの状態になります。

cacheTimeを5分から5秒に変更してToggleボタンを押してください。5秒後にキャッシュが使えなくなると同時にdevtoolから情報が消えます。

このことからdevtoolがキャッシュの情報を見ていることもわかります。devtoolを見ることでuseQueryの状態を目で見て確認することができます。

casheTime5秒を設定し5秒経過した状態
casheTime5秒を設定し5秒経過した状態

useQueryのドキュメントに記述された通り簡単にデータを取得、キャッシュ、サーバデータとの同期が行えることが確認できました。useQueryには本文書で記載したこと以外にもまだまだ機能を持ち、実際に本番で利用するためには理解を深めることが必要ですが、useQueryの基本部分は理解できたのではないでしょうか。