React Query使い方忘却録

# 内容
React Queryの学習用メモとしてまとめる。
※パフォーマンスは未考慮。
APIはjsonplaceholderを使用して、実装を確認。

# 開発環境

PC:macOS Monterey(Intel)
エディター：Visual Studio Code

パッケージマネージャー： yarn
パッケージ：
react: 18.1.0
react-query: 3.39.1
typescript: 4.6.4

# API
https://jsonplaceholder.typicode.com/

## useQuery

useQueryは一意のqueryKeyに基づいて、クエリを管理することができる。


useQuery.d.tsには以下の３つのuseQuery関数が記載されている。(一部省略)

```useQuery.d.ts
useQuery(options);
useQuery(queryKey, options?);
useQuery(queryKey, queryFn, options?);
```
type.d.tsファイルより、
queryKeyは文字列、配列で指定できる。
queryFnの戻り値は任意の型<T>かPromiseの任意の型<T>。
optionsは[こちら](https://react-query.tanstack.com/reference/useQuery)を参照。

```type.d.ts
export declare type QueryKey = string | readonly unknown[];
export declare type QueryFunction<T = unknown, TQueryKey extends QueryKey = QueryKey> = (context: QueryFunctionContext<TQueryKey>) => T | Promise<T>;
```

### useQueryサンプルコード

<details><summary>コードはこちら</summary>

```useTodoQuery.ts
// jsonplaceholderのtodosの型を定義
type TodoType = {
  userId: Number,
  id: Number,
  title: String,
  completed: boolean,
}

// queryFn
const fetchTodosQueryFn = async () => {
  const result = await fetch('https://jsonplaceholder.typicode.com/todos');

  if (result.status !== 200) {
    throw new Error('fetch Error')
  }
  return result.json();
}

// useQUery（queryKey='todos' , queryFn=fetchTodosQueryFn）
export const fetchTodoList = () => {
  return useQuery<TodoType[], Error>(
    'todos',
    fetchTodosQueryFn,
  );

    // オブジェクトとして書くことも可能
    // return useQuery<TodoType[], Error>(
    //   {
    //     queryKey: 'todos',
    //     queryFn: fetchTodosQueryFn,
    //   }
    // );
}
```

```Todos.tsx
import { fetchTodoList } from 'useTodoQuery';

export const Todos = () => {
  const { isLoading, isError, error, data } = fetchTodoList();

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

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

  if (!data) {
    return (
      <>
        none data
      </>
    );
  }

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id.toString()}>{todo.title}</li>
      ))}
    </ul>
  );
};

```
</details>

以下のように取得したtodoが表示されていることが確認できる。
![スクリーンショット 2022-06-26 16.33.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631339/019dc09d-e99f-4926-6e9b-2521a4ece234.png)

 ## useMutation

useMutationはデータの作成/更新/削除時に使用される。

useMutation.d.tsには以下の4つのuseMutation関数が記載されている。(一部省略)


```useMutation.d.ts
useMutation(options);
useMutation(mutationFn, options?); 
useMutation(mutationKey, options?);
useMutation(mutationKey, mutationFn?, options?);
```

### useMutationサンプルコード

<details><summary>コードはこちら</summary>

```useTodoQuery.ts
// jsonplaceholderのtodosの型を定義
type TodoType = {
  userId: Number,
  id: Number,
  title: String,
  completed: boolean,
}

const createTodoQueryFn = (body: NewTodoType) => axios.post<NewTodoType>('https://jsonplaceholder.typicode.com/todos', body).then(res => res.data);

const createTodoWithMutation = () => useMutation<NewTodoType, Error, NewTodoType>(
  'newTodo',
  async (body: NewTodoType) => {
    const result = await createTodoQueryFn(body);
    return result;
  },
  {
    onSuccess: (todo) => {
      console.log('Mutation', todo);
    },
    onError(error, variables, context) {
      console.log('error', error.message);
    },
  }
);
```

```TodoForm.tsx
import { Button, Box, Container, TextField, Typography } from '@mui/material';
import { SubmitHandler, useForm } from 'react-hook-form';
import * as yup from "yup";
import { yupResolver } from '@hookform/resolvers/yup';

import { useTodoList } from 'useTodoList';

// バリデーションルール
let todoSchema = yup.object({
  id: yup.number().default(1),
  title: yup.string().required('タイトルを入力してください'),
  completed: yup.boolean().default(false),
});

export const TodoInputForm = () => {
  const { createTodo, createTodoWithMutation } = useTodoList();

  const mutation = createTodoWithMutation();

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<TodoType>({ resolver: yupResolver(todoSchema) })

  const onSubmit: SubmitHandler<TodoType> = async (data) => {
    mutation.mutate(
      {
        id: data.id,
        title: data.title,
      });
  };

  return (
    <Container maxWidth="sm" sx={{ p: 5 }}>
      <Typography variant='h1' >Todo List</Typography>

      {mutation.isLoading ?
        <>
          作成中...
        </>
        :
        <Box
          sx={{
            display: 'flex',
            justifyContent: 'space-around',
          }}
        >
          <TextField
            required
            label="タイトル"
            type="title"
            {...register('title')}
            error={'title' in errors}
            helperText={errors.title?.message}
          />
          <Button
            color="primary"
            variant="contained"
            size="large"
            onClick={handleSubmit(onSubmit)}
          >
            新規作成
          </Button>
        </Box>
      }
    </Container>
  );
};
```
</details>

# 参考記事

[公式 React Query](https://react-query.tanstack.com/)

[JSONPlaceholider](https://jsonplaceholder.typicode.com/)