はじめに
Componentのバケツリレー問題をどのように解決すればいいかずっと悩んでました😅そんな問題を解決してくれる素晴らしいライブラリがありました。SWRです!!自分が抱えていたバケツリレーの問題をすぐ解決してくれました😊
今回は、SWRを使ってTodoアプリの状態管理の方法を解説していきます!完成した時のイメージがこちらです。
実行環境
- Next.js : 12.1.0
- React : 17.0.2
- TypeScript : 4.6.2
- TailwindCSS : 3.0.23
- SWR : 1.2.2
SWRとは?
SWRはデータ取得のための React Hooks ライブラリです。
SWRの特徴
たった 1 行のコードで、プロジェクト内のデータ取得のロジックを単純化し、さらにこれらの素晴らしい機能をすぐに利用できるようになります。
- 速い、 軽量 そして 再利用可能 なデータの取得
- 組み込みの キャッシュ とリクエストの重複排除
- リアルタイム な体験
- トランスポートとプロトコルにとらわれない
- SSR / ISR / SSG support
- TypeScript 対応
- React Native
SWR は、スピード、正確性、安定性のすべての面をカバーし、より良い体験を構築するのに役立ちます。
- 高速なページナビゲーション
- 定期的にポーリングする
- データの依存関係
- フォーカス時の再検証
- ネットワーク回復時の再検証
- ローカルキャッシュの更新(Optimistic UI)
- スマートなエラーの再試行
- ページネーションとスクロールポジションの回復
- React Suspense
でも、データ取得のためのライブラリなのにどうして状態管理ができるかのか?
それは、APIのリクエストをせずに状態が保持できるのが理由です。Shuさんという方が思いついたらしいです。
全体のコード
import type { NextPage } from 'next';
import { Card } from '@/component/Card';
import { Form } from '@/component/Form';
import { useTodo } from '@/hook/useTodo';
const Home: NextPage = () => {
const { todo } = useTodo();
return (
<div className='my-12 text-center'>
<Form />
<div className='grid grid-cols-2 justify-items-center mt-4'>
{todo.map((t, index) => (
<div key={index} className='mt-4'>
<Card title={t.title} task={t.task} i={index} />
</div>
))}
</div>
</div>
);
};
export default Home;
import React from 'react';
import { useTodo } from '@/hook/useTodo';
export const Form: React.FC = () => {
const { form, isUpdate, setForm, handleCreate, handleUpdate } = useTodo();
return (
<div>
<input
type='text'
placeholder='Task'
value={form.title}
onChange={(e) => setForm({ ...form, title: e.target.value })}
className='px-1 rounded-full border border-gray-300'
/>
<input
type='text'
placeholder='Todo'
value={form.task}
onChange={(e) => setForm({ ...form, task: e.target.value })}
className='px-1 mx-6 rounded-full border border-gray-300'
/>
{isUpdate === false ? (
<button onClick={handleCreate} className='py-0.5 px-2 text-white bg-blue-500 rounded-lg'>
作成
</button>
) : (
<button onClick={handleUpdate} className='py-0.5 px-2 text-white bg-pink-500 rounded-lg'>
更新
</button>
)}
</div>
);
};
import React from 'react';
import { useTodo } from '@/hook/useTodo';
interface CardProps {
title: string;
task: string;
}
export interface indexProps {
i: number;
}
export const Card: React.FC<CardProps & indexProps> = ({ title, task, i }) => {
const { handleEdit, handleDelete } = useTodo();
return (
<div className='py-4 w-[400px] rounded-lg border border-gray-300 shadow-md'>
<h1 className='text-3xl font-bold'>{title}</h1>
<p className='px-2 my-4 text-left'>{task}</p>
<div className='flex justify-end '>
<button
onClick={() => handleEdit(i)}
className='py-0.5 px-2 mr-2 text-white bg-green-500 rounded-lg'
>
編集
</button>
<button
onClick={() => handleDelete(i)}
className='py-0.5 px-2 mr-2 text-white bg-red-500 rounded-lg'
>
削除
</button>
</div>
</div>
);
};
import useSWR from 'swr';
interface TodoProps {
title: string;
task: string;
}
export const useFormSWR = (
key: string,
initialData: TodoProps,
): [TodoProps, (state: TodoProps) => void] => {
const { data: state, mutate: setState } = useSWR(key, null, { fallbackData: initialData });
return [state as TodoProps, setState];
};
export const useTodoSWR = (
key: string,
initialData: TodoProps[],
): [TodoProps[], (state: TodoProps[]) => void] => {
const { data: state, mutate: setState } = useSWR(key, null, { fallbackData: initialData });
return [state as TodoProps[], setState];
};
export const useIndexSWR = (
key: string,
initialData: number,
): [number, (state: number) => void] => {
const { data: state, mutate: setState } = useSWR(key, null, { fallbackData: initialData });
return [state as number, setState];
};
export const useUpdateSWR = (
key: string,
initialData: boolean,
): [boolean, (state: boolean) => void] => {
const { data: state, mutate: setState } = useSWR(key, null, { fallbackData: initialData });
return [state as boolean, setState];
};
import { useFormSWR, useTodoSWR, useIndexSWR, useUpdateSWR } from '@/hook/useStateSWR';
export const useTodo = () => {
const [form, setForm] = useFormSWR('form', { title: '', task: '' });
const [todo, setTodo] = useTodoSWR('todo', []);
const [index, setIndex] = useIndexSWR('index', 0);
const [isUpdate, setIsUpdate] = useUpdateSWR('isUpdate', false);
const handleCreate = () => {
setTodo([...todo, form]);
setForm({ title: '', task: '' });
};
const handleUpdate = () => {
const updateTodo = [...todo];
updateTodo.splice(index, 1, form);
setTodo(updateTodo);
setIsUpdate(false);
setForm({ title: '', task: '' });
};
const handleEdit = (i: number) => {
setIndex(i);
setForm(todo[i]);
setIsUpdate(true);
};
const handleDelete = (i: number) => {
const deleteTodo = [...todo];
deleteTodo.splice(i, 1);
setTodo(deleteTodo);
setForm({ title: '', task: '' });
setIsUpdate(false);
};
return {
form,
todo,
index,
isUpdate,
setForm,
setTodo,
setIndex,
setIsUpdate,
handleCreate,
handleEdit,
handleUpdate,
handleDelete,
};
};
他のファイルでも共有ができるstate変数を設定する
useStateSWR.tsx
で他のファイルでも共有ができるstate変数を設定します。
今回用意していくのは以下の4つを設定してます。
- フォームに入力されたデータを作成と更新をするためのstate変数
- 作成されたTodoリストを表示させるCardのstate変数
- Cardの編集ボタンを押したときはtrue、更新と削除ボタンを押したときはfalseを返すためのstate変数
- Cardの編集ボタンと削除ボタンを押したときに配列番号を取得するためのstate変数
それぞれのTypeについては類似しているので、useIndexSWR
だけを説明します。コードの意味はconst [state, setState] = useState(0)
と同じです。
export const useIndexSWR = (
key: string,
initialData: number,
): [number, (state: number) => void] => {
const { data: state, mutate: setState } = useSWR(key, null, { fallbackData: initialData });
return [state as number, setState];
};
initialDataについてはnumber型で設定してあげないといけないので、numberで設定してます。useIndexSWR
はCardコンポーネントを配列で返しており、編集ボタンと削除ボタンを押したときにその配列番号を保持するためようの変数なのでnumber型で設定してます。
useSWR
の第二引数をnullで明示的に設定することでAPIリクエストは行わないようにする。
useSWRの設定について調べると第三引数のfallbackDataがinitialDataの名前で書かれていた記事しかなかったです。現在useSWRの第三引数のOptionにはinitialDataの引数はないです。
SWRの公式ドキュメントではfallbackDataの引数が初期データを返す引数となってます。
カスタムフックで設定したstate変数を使ってロジックを書いていく
useStateSWR.tsx
で設定したstate変数は状態保持したまま他のファイルでも使用できるようになっています。なのでサイトUIのプレゼンテーションとビジネスロジックを切り離してコードを書くことができます。
useTodo.tsx
でstate変数の初期設定とTodoアプのCRUD処理を書いていきます。
import { useFormSWR, useTodoSWR, useIndexSWR, useUpdateSWR } from '@/hook/useStateSWR';
export const useTodo = () => {
const [form, setForm] = useFormSWR('form', { title: '', task: '' });
const [todo, setTodo] = useTodoSWR('todo', []);
const [index, setIndex] = useIndexSWR('index', 0);
const [isUpdate, setIsUpdate] = useUpdateSWR('isUpdate', false);
const handleCreate = () => {
setTodo([...todo, form]);
setForm({ title: '', task: '' });
};
const handleUpdate = () => {
const updateTodo = [...todo];
updateTodo.splice(index, 1, form);
setTodo(updateTodo);
setIsUpdate(false);
setForm({ title: '', task: '' });
};
const handleEdit = (i: number) => {
setIndex(i);
setForm(todo[i]);
setIsUpdate(true);
};
const handleDelete = (i: number) => {
const deleteTodo = [...todo];
deleteTodo.splice(i, 1);
setTodo(deleteTodo);
setForm({ title: '', task: '' });
setIsUpdate(false);
};
return {
form,
todo,
index,
isUpdate,
setForm,
setTodo,
setIndex,
setIsUpdate,
handleCreate,
handleEdit,
handleUpdate,
handleDelete,
};
};
あとはそれぞれのファイル(Form.tsx, Card.tsx, index.tsx
)にカスタムフックで設定したstate変数やビジネスロジックを渡してあげれば完成です。
最後に
SWRサイコーです😊SWRのライブラリのおかげでバケツリレー問題はすんなり解決できます!!
この記事が少しでも参考になれば幸いです。
最後までお読みいただきありがとうございました。