4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Next.jsでtodoアプリ を作ろうとしたらRecoil + React Hook Form v7で楽ができた件

Posted at

はじめに

なろう形式のタイトルってわかりやすいですね。
初投稿です。
Reactの学習のため、Todoアプリを作成しました。
どうせなら最新のライブラリを使用しようとRecoil, React Hook Form v7を使ってみましたので、備忘録がてらその内容を紹介します。

環境

Windows10 64bit(20H2)
WSL(Ubuntu 18.) v1
node v14.17.1
yarn 1.22.5
VSCode

完成図

こんな感じの簡単なtodoアプリを作ってみました。
見た目は完全に度外視しています。
スクリーンショット 2021-07-06 143548.png

install

まずWSLで下記コマンドを実行して、Next.js(TypeScript)のプロジェクトを作成します。

$ yarn create next-app --typescript
// 対話形式でプロジェクト名を聞かれるので、任意の名前を入力します。
What is your project named? … next-recil-todo

プロジェクトの中身はこのようになっています。

$ ls -1 next-recil-todo
README.md
next-env.d.ts
next.config.js
node_modules
package.json
pages
public
styles
tsconfig.json
yarn.lock

インストールできたので、動作確認します。

$ cd next-recil-todo
$ yarn dev

この画面が出ていれば成功です。
スクリーンショット 2021-07-06 105824.png

続いて使用するライブラリをインストールします。

$ yarn add recoil react-hook-form moment

これでインストールは完了です。

実装

コードはgithubに上げましたので、全体が見たい方は参照してください。
https://github.com/din-iis/next-recil-todo

atoms

まずはRecoilで使用するatomのファイルを配置するディレクトリを作成します。

$ mkdir -p src/atoms

作成したディレクトリに下記のファイルを作成します。

states.ts
import { atom } from "recoil";

export type Todo= {
    id: string
    title: string
    text: string
    isComplete: boolean
}

// Todoリストを保持
const todoListState = atom<Todo[]>({
    key: 'todoListState',
    default: [],
});

export { todoListState }

components

次にcomponetを管理するディレクトリを作成します。

$ mkdir src/components

作成したディレクトリに下記2つのファイルを作成します。

React Hook Form v7でRecoilを使用する際、validationを指定するには「 {...register(~」と記述する必要があります。
React Hook Form v6でRecoilでサンプルを記載しているサイトが多かったので、React Hook FormのVersionに注意してください。

src/components/edit.tsx
import { useSetRecoilState } from "recoil";
import { todoListState } from "../atoms/states";
import moment from "moment";
import { useForm } from 'react-hook-form'

type FormData = {
    id: string,
    title: string,
    text: string,
    isComplete: boolean,
}

const Edit: React.FC = () => {
    const setTodoList = useSetRecoilState(todoListState);

    // react-hook-formを設定
    const { register, handleSubmit, formState: { errors }, reset } = useForm<FormData>({
        mode: 'onChange',
        defaultValues: {
            id : moment().format('YYYYMMDDHHmmss'),
            title: '',
            text: '',
            isComplete: false,
        }
    })

    // Todoを追加
    const addTodo = (data: FormData) => {
        setTodoList((oldTodoList) => [
            ...oldTodoList,
            data,
        ]);
        reset()
    }

    return (
        <div>
            <form onSubmit={handleSubmit(addTodo)}>
                <label>title:</label>
                {/* 入力要素とvalidationを設定 */}
                <input
                    type="text"
                    {...register(
                        "title",
                        {
                            required: '必須項目です',
                            maxLength: {
                                value: 20,
                                message: '20文字以内で入力してください',
                            },
                        }
                    )}
                />
                {errors.title && <span>{errors.title.message}</span>}
                <br />
                <label>text:</label>
                {/* 入力要素とvalidationを設定 */}
                <input
                    type="text"
                    {...register(
                        "text",
                        {
                            required: '必須項目です',
                            maxLength: {
                                value: 20,
                                message: '20文字以内で入力してください',
                            },
                        }
                    )} />
                {errors.text && <span className="error.main">{errors.text.message}</span>}
                <br />
                <button type="submit">送信</button>
            </form>
        </div>
    )
}

export default Edit
src/components/list.tsx
import { useRecoilState, useSetRecoilState } from "recoil";
import { Todo, todoListState } from "../atoms/states";

const TodoList = () => {
    const [todoList, setTodoList] = useRecoilState(todoListState)
    // 完了したTodoを削除
    const deleteTodo = (id:string) => {
        const target = todoList.filter((todo) => {
            return (todo.id != id)
        })
        setTodoList(target);
    }

    return (
        <div>
            {todoList.map(item =>
                <div key={item.id}>
                    タイトル:{item.title} <br />
                    テキスト:{item.text} <br />
                    <button  onClick={() => { deleteTodo(item.id) }} >
                        完了
                    </button>
                </div>
            )}
        </div>
    )
}

export default TodoList

Recoilを使用したcomponetを表示します。

pages/_app.tsx
import React from 'react';
import type { AppProps } from 'next/app'
import { RecoilRoot } from 'recoil';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    // RecoilRootを設定
    <RecoilRoot>
      <Component {...pageProps} />
    </RecoilRoot>
  )
}

export default MyApp
pages/index.tsx
import TodoList from '../src/components/list'
import Edit from '../src/components/edit'

export default function Home() {
  return (
    <div>
      {/* componetを表示 */}
      <Edit />
      <TodoList />
    </div>
  )
}

まとめ

Reactの書籍ではReduxを使用していましたが分かりづらいのもあり、Recoilを使ってみたところ非常に簡単で使いやすかったです。
これからはもっと発展させてGraphQLとの連携を行ってみようと思います。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?