TanStack Query（旧React Query）でデータ更新を行う 

## はじめに

[前回、TanStack Query（旧React Query）を使ってみたの記事](https://qiita.com/nanndot/items/1102bc784934fcd8ac5d)ではデータ取得の方法しか紹介していなかったので、今回は更新処理について紹介させてください。

##  使用した環境

- Node.js 19.1.0
- React 18.2.0
- @tanstack/react-query 4.293
- axios 1.3.6
- react-router-dom 6.10.0
- express 4.18.2
- cors 2.8.5
- NeDB 1.8.0

## 事前準備

### 前提 

[前回の記事](https://qiita.com/nanndot/items/1102bc784934fcd8ac5d)で実装が最後まで完了していることを前提とします！

###  サーバー側の準備

リクエストを受けてユーザーを登録するエンドポイントを作成します。
ユーザー情報はDBに保存したいので簡易的なインメモリDBのNeDBをインストールしておきます。

```shell:実行コマンド
npm i nedb
```

```diff_javascript:server.js
const express = require("express");
const cors = require("cors");
+ const Datastore = require("nedb");

const app = express();
const router = express.Router();
+ const db = new Datastore();

app.use(cors());
app.listen(3000);
+ app.use(express.json());

+ // 初期データの設定
+ db.insert([
+   {
+     id: 1,
+     name: "tanaka",
+     description: "高身長"
+   },
+   {
+     id: 2,
+     name: "sato",
+     description: "体格が良い"
+   }
+ ]);

//ユーザー一覧取得
- router.get("/", (req, res, next) => {
-   //テストデータを返却
-   res.json([
-     {
-       id: 1,
-       name: "tanaka",
-       description:"高身長"
-     },
-     {
-       id: 2,
-       name: "sato",
-       description:"体格が良い"
-     },
-   ]);
- });
+ router.get("/", (req, res) => {
+   db.find({}, (err, data) => {
+     res.json(data);
+   });
+ });

//ユーザー追加
+ router.post("/", (req, res) => {
+   const newData = req.body;
+   db.insert(newData, (err, createdData) => {
+     res.json(createdData);
+   });
+ });

app.use(router);
```

ユーザー追加処理に加えて、前回作成したユーザー一覧取得もDBから取得した情報を表示するよう修正しました。

ユーザー一覧取得/ユーザー追加ともに動作確認をしておきましょう。

```shell:実行コマンド
node ./server.js
```

####  ユーザー一覧取得

![スクリーンショット 2023-07-23 16.24.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/4ce9a473-a18a-b443-8b13-eaa6f58fdd4a.png)

#### ユーザー追加

![スクリーンショット 2023-07-23 16.25.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/eea44d82-be6a-d58b-cd36-5b9fef6da78b.png)

ユーザー一覧取得/ユーザー追加ともに問題なく動いていますね！

## 実装

動作確認ができたので実装を進めていきます！

### カスタムフック

入力したユーザー情報をサーバーに送信し、成功したらキャッシュを更新するカスタムフックを作成します。

```javascript:useMutateUser.js
import { useQueryClient, useMutation } from "@tanstack/react-query";
import axios from "axios";

export const useMutateUser = () => {
  const queryClient = useQueryClient();
  const createUserMutation = useMutation(
    async (user) => await axios.post("http://127.0.0.1:3000/",user),
    {
      //通信が成功した際の処理
      onSuccess: (res) => {
        const user = res.data
        //キャッシュデータ取得
        const previousUsers = queryClient.getQueryData({ queryKey: ["users"] });
        if (previousUsers) {
          //キャッシュデータを更新
          queryClient.setQueryData({ queryKey: ["users"] }, [
            ...previousUsers,
            user,
          ]);
        }
      },
      //エラーが発生した際の処理
      onError: (error) => {
        console.log(error)
      },
    }
  );
  return { createUserMutation };
};
```

`onSuccess`にサーバーとの通信が成功した時の処理を記載します。`setQueryData`でクエリキーを指定して、更新したいデータを引数に渡すことでキャッシュデータの更新が可能です。
サーバー通信時のエラーハンドリングは`onError`に処理を記載します。

### ユーザー追加画面

キャッシュしたユーザー情報の表示と追加の画面を作成します。

```jsx:QueryAdd.jsx
import { useQueryClient } from "@tanstack/react-query";
import { Link } from "react-router-dom";
import "./QueryAdd.css";
import { useState } from "react";
import { useMutateUser } from "../hooks/useMutateUser";

const QueryAdd = () => {
  const queryClient = useQueryClient();
  //キャッシュから取得
  const users = queryClient.getQueryData({ queryKey: ["users"] });
  //ID
  const [id, setId] = useState("");
  //名前
  const [name, setName] = useState("");
  //備考
  const [description, setDescription] = useState("");
  const { createUserMutation } = useMutateUser();
	
  //追加ボタン押下時に実行
  const onClickAddButton = async () => {
    const user = { id, name, description };
    //ユーザー追加
    await createUserMutation.mutateAsync(user);
  };

  return (
    <>
      <h1>ユーザー一覧（キャッシュ）</h1>
      <ul>
        {users?.map((user) => {
          return (
            <li key={user.id}>
              <Link to={`/query/detail?id=${user.id}`}>{user.name}</Link>
            </li>
          );
        })}
      </ul>
      <h3>ユーザー追加</h3>
      <div className="row">
        <label>ID</label>
        <input
          type="number"
          value={id}
          onChange={(e) => setId(Number(e.target.value))}
        ></input>
      </div>
      <div className="row">
        <label>名前</label>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        ></input>
      </div>
      <div className="row">
        <label>備考</label>
        <textarea
          value={description}
          onChange={(e) => setDescription(e.target.value)}
        ></textarea>
      </div>
      <div>
        <input type="button" value="追加" onClick={onClickAddButton}></input>
      </div>
    </>
  );
};

export default QueryAdd;
```
カスタムフックから`createUserMutation`を取得し、`mutateAsync()`でサーバーと通信してユーザー登録を行います。
### ユーザー一覧画面

ユーザー追加画面へ遷移するリンクを追加します。

```diff_jsx:QueryFetch.jsx
import { useQueryUsers } from "../hooks/useQueryFetchHook";
import { Link } from "react-router-dom";

const QueryFetch = () => {
  const { status, data } = useQueryUsers();
  console.log("QueryFetchが描画されました。");
  if (status === "loading") return <div>{"Loading..."}</div>;
  if (status === "error") return <div>{"Error"}</div>;

  return (
    <>
      <h1>ユーザー一覧</h1>
      <ul>
        {data?.map((user) => {
          return (
            <li key={user.id}>
              <Link to={`/query/detail?id=${user.id}`}>{user.name}</Link>
            </li>
          );
        })}
      </ul>
+       <Link to={"/query/add"}>ユーザー追加画面</Link>
    </>
  );
};

export default QueryFetch;
```

### App.jsx

最後にユーザー追加画面へのルーティングを追加します。

```diff_jsx:App.jsx
import ContextFetch from "./components/ContextFetch";
import QueryFetch from "./components/QueryFetch";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import UserDataContextProvider from "./Contexts/UserDataContext";
import ContextFetchDetail from "./components/ContextFetchDetail";
import QueryFetchDetail from "./components/QueryFetchDetail";
import { BrowserRouter, Route, Routes } from "react-router-dom";
+ import QueryAdd from "./components/QueryAdd";
const queryClient = new QueryClient();

function App() {
  return (
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <UserDataContextProvider>
          <Routes>
            <Route path="/query" element={<QueryFetch />} />
+           <Route path="/query/add" element={<QueryAdd />} />
            <Route path="/query/detail" element={<QueryFetchDetail />} />
            <Route path="/" element={<ContextFetch />} />
            <Route path="/detail" element={<ContextFetchDetail />} />
          </Routes>
        </UserDataContextProvider>
      </QueryClientProvider>
    </BrowserRouter>
  );
}

export default App;
```

## 動作確認

`http://localhost:5173/query/`にアクセスしてユーザー一覧画面を開きます。
![スクリーンショット 2023-07-15 18.19.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/b060f45d-9cb4-f1f7-0dba-a03d85ef59fa.png)ユーザー追加リンクを押下して、ユーザー追加画面を開きます。キャッシュされた情報とユーザー追加入力欄が表示されます。
![スクリーンショット 2023-07-15 18.21.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/a45bce46-455b-3810-f343-ab9137de5835.png)ユーザー情報を入力して追加ボタンを押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/cc99296c-1f88-4adb-02d5-07c2d1a5e0cf.png)追加ボタンを押下すると一覧に伊藤さんが追加されてキャッシュ情報も更新されていることが分かりますね！
伊藤さんのリンクを押下して詳細画面も確認してみましょう。
![05839747-0991-bd5e-23a1-0c93efa3f716.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178305/953d2d08-8090-a9e7-01f3-a8e89ab786f1.png)詳細画面にもキャッシュした情報が反映されていますね！
Tanstack Queryを使った更新処理はこれで完了です！

## おわりに

Tanstack Queryを使った更新処理も紹介させていただきました。
サーバーとの通信を行う場合は便利なので使用することをお勧めします！