React-Queryを開始する前の前提条件ございます。
・ React フックと関数コンポーネントに関する中級レベルのスキルが不可欠です。
・ JavaScriptでのREST APIとデータ取得の基本的な理解。
・マシンにNode.jsがインストールされていることを確認してください。
React-Queryとは?
React-Queryは、ReactおよびNext.jsコードベース用に構築されたライブラリであり、ネットワークリクエストを作成する際に従うべきすべてのベストプラクティスが組み込まれています。
ReactおよびNext.jsアプリでのサーバー状態の取得、キャッシュ、同期、および更新を簡単にする React用のデータ取得ライブラリという事です。
これは、キャッシュをフェッチしてサーバーからデータを同期する方法を簡素化するReact ライブラリであり、Reactの不足しているデータフェッチライブラリとしてよく説明されますが、より技術的な用語で言えば、React アプリケーションで上記のすべてのプロセスを簡単に実行できます。
React自体には、サーバーからデータをフェッチする方法に関する意見はありません。
最も基本的なアプローチは、コンポーネントが最初に useEffectにマウントされたときにブラウザのフェッチAPIを使用し、次にuseStateを使用して応答を管理することです。
このプロセスは機能しますが、キャッシュ、再試行、重複排除などの要件がある場合です。
データ取得コードを簡素化するだけでなく、これらの複雑な要件をすぐに処理できます。
つまり、React-Queryを使用すると、グローバルな状態を変更することなく、シンプルかつ宣言的な方法でReactベースのアプリケーションのデータをフェッチ、キャッシュ、および更新できます。
React-Queryは、Reactのエコシステムで最も人気のあるライブラリの1つになりつつあります。
ReduxやuseEffectとの違いは?
React-QueryはAxiosやfetch APIと同じではなく、ReduxまたはContext APIの代替品でもないことを明確にする必要があります。
React-Queryは主にデータ同期に使用され、Reduxはグローバルな状態管理に使用されます。
useEffectなどの一般的なデータ取得パターンとの違いは、React-Queryは前回取得したデータを返し、その後もう一度取得し直すという点です。
もしリソースが最初と同じであれば、React-Queryはページの再読み込みを強制することなく、両方のデータを参照として保持致します。
React-Queryを使用する理由
これはデータを取得するだけなのか?私たち開発者は、JS FetchAPI
またはAxios
とuseEffect
およびuseState
フックを使用してそれらを行ってきました。
しかし、突然useEffectと useStateの代わりに React-Query
を使用するのはなぜなのか?
ご存じかもしれませんが、ほとんどの従来の管理ライブラリはクライアント状態の操作には優れていますが、非同期またはサーバー状態の操作では煩わしくなりました。
React-Queryを使用することで、特に使いやすくそしてゼロ構成であり、必要に応じてカスタマイズできる場合は、これらのことに余計な注意を払う必要がありません。
React Queryは、グローバルな状態がなくても、データの取得、キャッシュ、更新を簡素化します。
頻繁な更新、キャッシュ、およびサーバーとの同期が必要な非同期データを扱う場合、React-Queryに勝るライブラリはありません。
React-Queryを使用したデータ取得
CRAで新しいReact JSプロジェクトを作成し、react-query
をインストールします。
npm i react-query // or yarn add react-query
次に、App.js
ファイルでreact-queryからuseQuery
をインポートします。
// App.js import { useQuery } from "react-query";
単純なfetchData
関数を記述してRest APIからデータをフェッチします。
本日はfetch()
を使用していきますが、Axiosやその他の方法でも可能です。
JSONプレースホルダーAPI にネットワークリクエストを行うasync関数を定義する必要があります。
// App.js const fetchData = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users"); return res.json(); };
そしてuseQueryフックを使用して、下記のようにデータ取得を管理できます。
const response = useQuery("users", fetchData);
useQueryフックには2つの引数が必要となります。
1つはこのクエリのkey
です。
そのために、文字列のusers
を使用しています。
最初の引数として配列を置くこともでき、配列が渡された場合では各アイテムは安定したクエリキーにシリアル化されます。
2つ目は、データをフェッチする関数となります。
先述で作成したfetchDataのasync関数を渡します。
また、さまざまなオプションの3番目の引数としてオブジェクトを渡すことが可能です。
useQueryから返される応答は非常に重要となっています。
下記のプロパティがあります。
・ data
・ error
・ failureCount
・ isError
・ isFetchedAfterMount
・ isFetching
・ isIdle
・ isLoading
・ isPreviousData
・ isStale
・ isSuccess
・ refetch
・ remove
・ status
data
は取得した実際のデータです。
status
は、応答に応じてloading、error、success、またはidleになります。
上記これらすべてのプロパティにはさまざまな用途があります。
本日は、data
とstatus
、 error
およびisError
プロパティを使用する例を示します。
const { data, status } = useQuery("users", fetchData);
前述したように、useQueryフックは2つの引数を取り、最初の引数はキャッシュする文字列であり、クエリ結果を追跡します。
2番目の引数は、HTTPリクエストを作成するために定義するfetchData
関数です。
React-Queryは、キャッシュとデータの更新を内部的に処理します。
これで、データを使用してブラウザに表示する事ができます。
// App.js import { useQuery } from "react-query"; const fetchData = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users"); return res.json(); }; const App = () => { const { data, status } = useQuery("users", fetchData); return ( <div className="App"> {status === "error" && <p>Error fetching data</p>} {status === "loading" && <p>Fetching data...</p>} {status === "success" && ( <div> {data.map((user) => ( <p key={user.id}>{user.name}</p> ))} </div> )} </div> ); }; export default App;
上記で行った事は、status
を確認してデータを表示することです。
これは、React-QueryのuseQueryフックの使用方法を簡単に説明したものとなります。
QueryClientProvider
基本的には、React.jsアプリケーションでReact-Queryを構成するには、データ取得を必要とする最上位のコンポーネントをQueryClientProvider
コンポーネントでラップする必要があります。
QueryClientは、他のコンポーネントがキャッシュを利用できるようにします。
React-Queryを起動するために、ルートディレクトリのファイルindex.js
に下記のような基本的な設定をします。
// index.js v18〜 import React from 'react'; import { createRoot } from 'react-dom/client'; import 'index.css'; import App from './App'; import { QueryClient, QueryClientProvider } from "react-query"; const root = ReactDOM.createRoot(document.getElementById('root')); const queryClient = new QueryClient(); root.render( <React.ScriptMode> //ラップ <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> </React.ScriptMode> );
// index.js import React from 'react' import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import { QueryClient, QueryClientProvider } from "react-query"; const queryClient = new QueryClient(); ReactDOM.render( //ラップ <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider>, document.getElementById('root') );
QueryClientProvider
の子コンポーネントは、React-Queryライブラリが提供するフックにアクセスできるようになり、QueryClient
インスタンスが提供されるようになりました。
このインスタンスを使用し、React-Queryライブラリが提供するフックにアクセスすることになります。
クエリクライアントをインスタンス化し、コンポーネントツリーに提供すると、すべての子コンポーネントがプロのようにデータをフェッチできるようになります。
これは開発中のデバッグに使用される事も多いです。
import {useQuery} from 'react-query'; const fetchData = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users"); return res.json(); }; function Users(){ const {data, status} = useQuery('users', fetchData) if(status === "Loading"){ return <div>Loading...</div> } if(status === "isError"){ return <div>Loading…</div> } return( <div className='container'> <h1>Users</h1> { data.map((user, index) => { return <li key={index}>{user.title}</li> }) } </div> ) } export default Users;
コンポーネントの return
キーワードの前に、基本的なロジックを実行し、アプリケーションがロード状態にあるかどうか、またはエラーが発生したかどうかを確認します。
useQueryから返される応答にerror
とisError
プロパティを使用するのが基本です。
const {data, status, error, isError} = useQuery('users', fetchData) ; if(status === "Loading"){ return <div>Loading...</div> } if(isError){ return <div>Error! {error. message}</div> }
React-Queryを使用すると、エラー処理も簡単になります。
上記の通り、React-Queryはサーバーの状態を管理するための最良のライブラリの1つです。
アプリデータがユーザーを制御し始める前に制御することが可能となります。
クエリを並列で実行したい場合は、useQueryを並べて書けば問題ありません。
ただし、配列に基づいてクエリしたい場合や、Suspenseを使っている場合はuseQueriesを使う必要がありますので注意して下さい。
const userQueries = useQueries( users.map((user) => { return { queryKey: ['user', user.id], queryFn: () => fetchUserById(user.id), }; }), )``;
もう少し、詳しく学ばれたい方は下記へ進んで下さい。
クエリ
クエリするには下記の2つが必要となります。
・ ユニークな独自のKey
・ Promise(データをresolveするか、またはエラーを投げる)
const result = useQuery('users', fetchData); // or const result = useQuery({ queryKey: 'users', queryFn: fetchData, });
・ isLoading or status === 'loading'
初期ロード中
・ isError or status === 'error'
エラーが発生した状態
・ error
エラー内容
・ isSuccess or status === 'success'
データ取得が成功した状態
・ isIdle or status === 'idle'
クエリが無効された状態
・ isFetching or isLoading
なんらかの通信中である
クエリキー
・ クエリキーに基づいてキャッシュが行われる。
・ シリアライズ可能な値ならなんでもKeyとして使用できる。
useQuery('users', ...) // queryKey === ['users'] useQuery(['user', 5], ...) // queryKey === ['user', 5] useQuery(['user', 5, { preview: true }], ...) // queryKey === ['user', 5, { preview: true }]
クエリが特定のid等に基づいて行われるものであるならば、その値をキーとして含めておくことが重要です。
function Users({ userId }) { const result = useQuery(['users', userId], () => fetchDataById(userId)); }
クエリを実行する関数
データ取得に失敗した時は必ずエラーを投げることです。
axios
とは異なり、fetch
はデフォルトではエラーを投げないので注意する必要があります。
useQuery(['users', userId], async () => { const { ok, json } = await fetch('/users/' + userId); if (!ok) { throw new Error('ネットワークのレスポンスが良くありません。'); } return json(); });
バックグラウドでのデータ取得をユーザーに通知
・ isFetching
ではあらゆる通信でtrue
となります。
・ idLoading
は初回データ取得の時だけtrue
となります。
・ アプリ全体での通信状態を取得したい場合ではuseIsFetching
を使用して下さい。
Mutations
データの作成・更新・削除する場合はuseQueryではなくuseMutation
を使用します。
const { status, isLoading, isError, isSuccess, mutate, error, data, } = useMutation((newUser) => axios.post('/users', newUser)); mutate({ id: 1234, title: 'test' });
クエリの無効化と停止
enabled
オプションをfalse
設定することで下記のようになります。
・ キャッシュデータが存在する場合は、isSuccess
状態になり、data
が提供されます。
・ キャッシュデータがない場合はisIdle
状態になり、マウント時にはクエリが実行されません。
・ バックグラウンドでは再クエリされません。
・ invalidateQueries
およびrefetchQueries
が発火されても再クエリしません。
・ refetch
を使って手動でクエリを実行が可能です。
enabledオプションは、クエリの実行結果を使って別のクエリを実行する際に使用します。
const { isIdle, data: projects } = useQuery( ['projects', userId], getProjectsByUser, { // userIDが存在する場合のみこのクエリを実行する enabled: !!userId, }, );
React-Queryライブラリはまた別途記事に致します。
最後に
ほとんどの場合、今まではReact JSでのプロジェクトの状態管理にはRedux
やContext API
を使用してきました。
React-Queryライブラリは、Webサービスのリクエストに関連するデータを管理するのに役立ち、ユーザーエクスペリエンスを向上させながら、複雑さが増してもアプリケーションを保守しやすくします。
コードベースを維持しやすくするために、コンポーネント内で直接React-Queryを使用するのではなく、カスタムフックを作成してクエリを処理することをお勧めします。
React-Queryのようなライブラリを使用すると、データ取得のためにカスタムライブラリを維持する必要がなくなります。
現在では、React-Queryがより強力になり、コミュニティの間で人気が高まっていると思います。
これは、すべてのReact開発者にお勧め致します。
本日は以上となります。
最後まで読んで頂きありがとうございます。