TanStack Queryを使ってみる
こんにちは!
最近、お仕事の関係で、TanStack Query(旧:React Query)について学ぶ必要が出てきました。折角なので、TanStack Query を使って簡単に実装を行なってみました!
ということで、本記事では、TanStack Query について、基本的な使い方や自分が行なった実装について、まとめました!
TanStack Query とは?
TanStack Query とは、データフェッチライブラリです。
API からデータを取得したい時や更新したい時に使うことができます。また、取得したデータはキャッシュされ、キャッシュされたデータを取り出すことも可能です。
基本的な使い方
とりあえず、基本的な使い方を見ていきたいと思います。
useQuery
・useMutation
・getQueryData
について主に取り上げていきます。
準備
まずは、TanStack Query を使用するための準備をしていきます。
インストール
インストールはnpm
・yarn
それぞれ次のコマンドになります。
# npm
$ npm i @tanstack/react-query
# yarn
$ yarn add @tanstack/react-query
index.tsx の修正
index.tsx
の修正を行なっていきます。
index.tsx
では、TanStack Query を使うための設定を行います。
const queryClient: QueryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});
まず、キャッシュを利用するために、QueryClient
というクラスのインスタンスを作成します。
インスタンス生成の引数として、defaultOptions
プロパティを持つオブジェクトを渡すことができます。
上記のコードでは、このdefaultOptions
プロパティのqueries
プロパティに、refetchOnWindowFocus: false
と設定されています。これは、TanStack Query がデフォルトでコンポーネントにフォーカスが当たるとフェッチが作動してしまうのを無効にします。
あとは、QueryClientProvider
に先ほど用意したqueryClient
を渡して、アプリケーション全体で利用できるようにするだけです。
const root: ReactDOM.Root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
これで、TanStack Query を使用するための準備は完了です。
データ取得
データの取得についてです。
データ取得には、useQuery
というフックを使います。
const { isLoading, data } = useQuery({
queryKey: ['任意の文字列'] // 配列
queryFn: exampleFetchFunc // データ取得の関数
});
useQuery
フックの引数に、queryKey
とqueryFn
をプロパティとして持つオブジェクトを渡しています。
queryKey
は、データを再取得する時やキャッシュ管理などで利用されます。文字列の配列で渡します。
queryFn
は、データを取得するための関数を渡します。実装した関数を渡すことができます。別で関数を実装せずに、アロー関数をそのまま渡すことも可能です。
data
だけでなく、isLoading
でローディング状態についても取得できます。
データ更新
続いて、データの更新です。
データの追加や変更などを行うには、useMutation
フックを使います。
const mutation = useMutation({
mutationFn: exampleMutateFunc // データ更新の関数
});
useMutation
フックの引数に、mutationFn
というプロパティを持つオブジェクトを渡します。
mutationFn
には、データを更新する関数を入れます。
フォームから受け取ったデータを使って更新する場合があると思います。そういった、データ更新の関数実行時に引数を渡す必要がある場合には、以下のように、アロー関数で引数を渡すことになります。
const mutationWithArg = useMutation({
mutationFn: (arg) => exampleMutateWithArgFunc(arg) // データ更新の関数
});
// データ更新実行
const onSubmit = (formData) => {
mutationWithArg.mutate(formData);
};
キャッシュされたデータを利用する
最後に、キャッシュされたデータの利用についてです。
キャッシュされたデータの取り出しには、useQueryClient
フックとgetQueryData
メソッドを使います。
データ取得useQuery
を実行したコンポーネントとはまた別のコンポーネントで、取得したデータを使いたい場合などあると思います。そういった時に、このuseQueryClient
フックとgetQueryData
メソッドでキャッシュされたデータを取り出してくることで、データを利用することができます。
const queryClient: QueryClient = useQueryClient();
const data = queryClient.getQueryData(['取り出したいqueryKey']);
useQueryClient
フックは、その時点のQueryClient
取得できます。この中に、キャッシュされたデータやdefaultOptions
の設定情報などが含まれています。
QueryClient
クラスには、getQueryData
メソッドが定義されているので、それを使ってキャッシュされたデータの取得を行います。getQueryData
メソッドの引数には、取り出したいキャッシュのqueryKey
の配列を渡します。ここは、queryKey
の配列を代入した変数を別で定義して、その変数を渡すことも可能です。
API を使って実践
ここからは、先ほど説明したフックやメソッドを利用して、API からのデータ取得・更新(追加)・利用を実践してみたいと思います。
データを取得して表示する
まずは、データを取得して表示するところから進めます。
データを取得する関数
API からデータを取得する関数、getData
を実装します。
/src/ts/getData.ts
を作ります。
import axiosInstance from '../axios/axiosInstance';
import { AxiosResponse } from 'axios';
import { GetResultDataType } from '../types/GetResultDataType';
const getData = async (): Promise<GetResultDataType> => {
const result: AxiosResponse = await axiosInstance.get('/dynamoDB/getItems');
const resultData: GetResultDataType = result.data;
return resultData;
};
export default getData;
API 呼び出しには、axios
を利用しています。
/dynamoDB/getItems
に GET リクエストを送ることで、API からデータをレスポンスとして返してもらいます。
useQuery でデータ取得
それでは、TanStack Query のuseQuery
と用意したデータを取得する関数getData
を利用して、データ取得を実装してみます。
/src/App.tsx
を編集していきます。
const App = (): JSX.Element => {
const { data }: UseQueryResult<GetResultDataType | undefined> = useQuery({
queryKey: ['data'],
queryFn: getData,
});
return (
<div>
<div>
<ul>
{data?.Items.map((item, index) => (
<li key={index}>{item.weight}</li>
))}
</ul>
<p>現在のデータ数:{data?.Count}個</p>
</div>
</div>
);
};
export default App;
データ取得部分だけ取り出して見てみます。
const { data }: UseQueryResult<GetResultDataType | undefined> = useQuery({
queryKey: ['data'],
queryFn: getData,
});
TanStack Query のuseQuery
フックを使用しています。
queryKey
には、data
という文字列を要素とする配列を渡しています。これが、取得してきたデータの再取得やキャッシュから利用する場合に使われます。
queryFn
には、getData
が渡されています。このgetData
が、先ほど実装した API からのデータ取得関数getData
です。
このuseQuery
フックを利用して取得したデータは、一部をmap
メソッドで展開して箇条書きリストで表示しています。
また、DynamoDB のテーブルに含まれる項目数もCount
というプロパティに入って返ってくるので、「現在のデータ数」として表示させています。
<ul>
{data?.Items.map((item, index) => (
<li key={index}>{item.weight}</li>
))}
</ul>
<p>現在のデータ数:{data?.Count}個</p>
ブラウザで確認するとこんな感じです。
箇条書きリストで、2つのデータが表示されています。現在のデータ数にも、「2個」と表示されています。
フォーム送信してデータを追加する
それでは、フォームに値を入力して、データを追加する実装も行なっていきます。
データを追加する関数
データを追加する関数、postData
を実装します。
/src/ts/postData.ts
を作ります。
import axiosInstance from '../axios/axiosInstance';
import { AxiosResponse } from 'axios';
import { FormDataType } from '../types/FormDataType';
const postData = async (formData: FormDataType): Promise<void> => {
const result: AxiosResponse = await axiosInstance.post(
'/dynamoDB/addItem',
formData
);
console.log(result);
};
export default postData;
フォームに入力された値をリクエストボディに入れて、/dynamoDB/addItem
に POST リクエストを送ることで、DynamoDB への項目(データ)追加を行います。
useMutation でデータ追加
それでは、TanStack Query のuseMutation
と用意したデータを追加するための関数postData
を利用して、フォームからのデータ追加を実装してみます。
/src/RecordingForm.tsx
でRecordingForm
コンポーネントを作っていきます。
フォーム実装には React Hook Form を、日付選択には react-datepicker を、日付の整形には date-fns を使用しています。
また、実際には、フォームのバリデーションも行なっていますが、見やすさのため削除しています。
type FormValuesType = {
date: Date;
weight: string;
};
const RecordingForm = (): JSX.Element => {
const {
register,
handleSubmit,
reset,
control,
formState: { errors },
} = useForm<FormValuesType>();
const queryClient: QueryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (formData: FormDataType) => postData(formData),
onSettled: () => {
queryClient.invalidateQueries(['data']);
},
});
const onSubmit = async (data: FormValuesType): Promise<void> => {
// 日付フォーマット
const date: Date = new Date(data.date);
const dataYear: number = date.getFullYear();
const dataMonth: number = date.getMonth();
const dataDate: number = date.getDate();
const formattedDate: string = format(
new Date(dataYear, dataMonth, dataDate),
'yyyy/MM/dd'
);
// 体重(Stringで入ってくるので、Numberにする必要がある)
const weightFloat: number = parseFloat(data.weight);
// 追加するデータのオブジェクト
const formData: FormDataType = {
date: formattedDate,
timestamp: date.getTime(),
weight: weightFloat,
};
// DynamoDBへ登録する関数実行
mutation.mutate(formData);
// フォームリセット
reset();
};
return (
<div>
<h2>フォーム</h2>
<form>
<div>
<label htmlFor='date'>Date</label>
<Controller
name='date'
control={control}
render={({ field: { onChange, value } }) => (
<DatePicker
selected={value}
onChange={onChange}
locale={ja}
id='date'
placeholderText='日付を選択してください'
/>
)}
/>
</div>
<div>
<label htmlFor='weight'>
Weight
</label>
<input
id='weight'
placeholder='入力してください'
{...register('weight')}
/>
<span>kg</span>
</div>
<div>
<button onClick={handleSubmit(onSubmit)}>
追加する
</button>
</div>
</form>
</div>
);
};
export default RecordingForm;
「追加する」ボタン押下で、onSubmit
が走るようになっています。
onSubmit
にフォーカスして見てみます。
const onSubmit = async (data: FormValuesType): Promise<void> => {
// 日付フォーマット
const date: Date = new Date(data.date);
const dataYear: number = date.getFullYear();
const dataMonth: number = date.getMonth();
const dataDate: number = date.getDate();
const formattedDate: string = format(
new Date(dataYear, dataMonth, dataDate),
'yyyy/MM/dd'
);
const timestamp: number = date.getTime();
// 体重(Stringで入ってくるので、Numberにする必要がある)
const weightFloat: number = parseFloat(data.weight);
// 追加するデータのオブジェクト
const formData: FormDataType = {
date: formattedDate,
timestamp: timestamp,
weight: weightFloat,
};
// データ追加処理実行
mutation.mutate(formData);
// フォームリセット
reset();
};
まずはフォームに入力された値を整形するなど処理を行なっています。それらの処理を行なって、formData
というオブジェクトにしています。
続いて、TanStack Query を使ったデータの追加部分にフォーカスを当てます。
const queryClient: QueryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (formData: FormDataType) => postData(formData),
onSettled: () => {
queryClient.invalidateQueries(['data']);
}
});
TanStack Query でデータ更新を行う、useMutaion
を使っています。
mutationFn
には、データ更新を行う関数として、先ほど実装したデータを追加する関数postData
を渡しています。POST リクエストでリクエストボディにはフォームで入力した値に少し手を加えたオブジェクトを渡したいので、formData
を引数に渡しています。mutationFn
実行後に再度データを取り直したいので、onSettled
には、invalidateQueries
メソッド(引数としてqueryKey
を渡している)によってキャッシュを無効にしています。これにより、データ追加を実行する度に、データを再度取り直して最新のデータが表示できるようになります。
useMutation
を実行するように定義したmutation
ですが、実際にはどのように実行されているのでしょうか。再び、onSubmit
の中を見てみます。
// 追加するデータのオブジェクト
const formData: FormDataType = {
date: formattedDate,
timestamp: timestamp,
weight: weightFloat
};
// データ追加処理実行
mutation.mutate(formData);
先ほどのmutation
のmutate
メソッドが実行されています。オブジェクトformData
が引数として渡されています。このformData
がmutationFn
の関数postData
の引数として渡される、つまり、POST リクエストを送るときのリクエストボディになります。
RecordingForm
コンポーネントができたので、/src/App.tsx
に追加します。
return (
<div>
<div>
{/* ↓追加 */}
<RecordingForm />
<ul>
{data?.Items.map((item, index) => (
<li key={index}>{item.weight}</li>
))}
</ul>
<p>現在のデータ数:{data?.Count}個</p>
</div>
</div>
);
別のコンポーネントでデータを利用する
最後に、取得したデータを別のコンポーネントで利用する実装を行います。
取得したデータを使って、グラフを作成したいです。グラフ表示には、Recharts を利用しています。
/src/Graph.tsx
でGraph
コンポーネントを作っていきます。
const Graph = (): JSX.Element => {
const queryClient: QueryClient = useQueryClient();
const queryKey: string[] = ['data'];
const queryData: GetResultDataType | undefined =
queryClient.getQueryData(queryKey);
return (
<div>
<h2>グラフ</h2>
{queryData?.Count === 0 ? (
<p>データがありません。</p>
) : (
<>
<div>
<LineChart width={900} height={300} data={queryData?.Items}>
<CartesianGrid strokeDasharray='3 3' />
<XAxis
dataKey='date'
interval={0}
angle={-16}
dx={-20}
dy={8}
tick={{
fontSize: 10,
}}
/>
<YAxis dataKey='weight' domain={[30, 50]} />
<Tooltip />
<Line type='monotone' strokeWidth={2} dataKey='weight' />
</LineChart>
</div>
<p>※X軸スクロールできます。</p>
</>
)}
</div>
);
};
export default Graph;
Graph
コンポーネントは、useQuery
でデータ取得したコンポーネントとは別のコンポーネントになります。そのため、コンポーネントで取得したデータを利用するためには、キャッシュからデータを取り出す必要があります。キャッシュからデータを取り出している部分にフォーカスを当てて見てみます。
const queryClient: QueryClient = useQueryClient();
const queryKey: string[] = ['data'];
const queryData: GetResultDataType | undefined =
queryClient.getQueryData(queryKey);
まずは、useQueryClient
フックで、今のQueryClient
を取得しています。QueryClient
には、キャッシュの情報も含まれています。
変数queryKey
には、取得したいキャッシュのqueryKey
の配列を代入しています。このqueryKey
は、データ取得時や再取得時にも渡していました。
そして、getQueryData
メソッドを実行し、キャッシュされたデータを取り出して、queryData
に代入しています。
これで、このコンポーネントでも、取得したデータを利用できる状態になりました。
それでは、グラフ実装の部分を簡単に見ていきます。
return (
<div>
<h2>グラフ</h2>
{queryData?.Count === 0 ? (
<p>データがありません。</p>
) : (
<>
<div>
<LineChart width={900} height={300} data={queryData?.Items}>
<CartesianGrid strokeDasharray='3 3' />
<XAxis
dataKey='date'
interval={0}
angle={-16}
dx={-20}
dy={8}
tick={{
fontSize: 10,
}}
/>
<YAxis dataKey='weight' domain={[30, 50]} />
<Tooltip />
<Line type='monotone' strokeWidth={2} dataKey='weight' />
</LineChart>
</div>
<p>※X軸スクロールできます。</p>
</>
)}
</div>
);
キャッシュから取り出したデータqueryData
のCount
プロパティが0、つまり、DynamoDB のテーブルにデータが何も入っていない場合は、グラフを表示しないので、「データがありません。」と表示するようにしています。
LineChart
に、data
が渡されています。このdata
に渡されているのが、キャッシュから取り出したデータqueryData
のItems
プロパティです。このItems
プロパティには、DynamoDB のテーブルに格納されている項目(データ)が、配列型式で入っています。data
属性に表示したいデータを渡すことで、そのデータを使ったグラフを表示することができます。
最後に、Graph
コンポーネントができたので、/src/App.tsx
に追加します。
グラフでデータが表示できているので、箇条書きで表示していた部分は削除しています。
return (
<div>
<div>
<RecordingForm />
{/* ↓追加 */}
<Graph />
<p>現在のデータ数:{data?.Count}個</p>
</div>
</div>
);
それでは、いくつかフォームからデータを追加し、ブラウザでの表示を確認してみます。
無事、フォームからのデータ追加とグラフ表示ができているようです 🎉
おわりに
データフェッチライブラリ、TanStack Query をテーマに取り上げてみました。
データ追加を行なった時に再度データ取得を実行してくれることや、Redux・Recoil のような状態管理ライブラリを使わずに別コンポーネントでも利用できることなど、API からデータを取得するような時には色々と便利なライブラリだと感じました!
お読み下さり、ありがとうございました。
Discussion