【Next.js】Recoil + React Hook Formで入力/確認画面を表示する

今回は、「Next.js + Typescript」で「Recoil」と「React Hook Form」を使用した、入力画面と入力した情報を表示する確認画面の実装方法の例を紹介していきます。

スポンサーリンク

検証環境

検証に使用した環境/ライブラリを次に記載します。

  • React
    • バージョン:18.2.0
  • Next.js
    • バージョン:12.1.6
  • Recoil
    • バージョン:0.7.4
  • React Hook Form
    • バージョン:7.32.2

動作例

「Recoil」と「React Hook Form」を使用した入力画面の内容を確認画面で表示する処理の動作例は、次のサイトで確認することができます。

【動作例】

Sandbox - CodeSandbox

【プロジェクト情報】

Recoil + React Hook Form - Codesandbox
Recoil + React Hook Form

プログラム内容

パッケージ構成

パッケージ構成の「package.json」は、次のようになります。

{
  "name": "recoil-react-hook-form",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "12.2.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-hook-form": "7.32.2",
    "recoil": "0.7.4"
  },
  "devDependencies": {
    "@types/node": "14.14.31",
    "@types/react": "17.0.2",
    "typescript": "4.1.5"
  },
  "license": "MIT",
  "keywords": [],
  "description": ""
}

ディレクトリ構成

ディレクトリ構成は、次のようになります。

.
|--pages
|  |--_app.tsx
|  |--confirm.tsx
|  |--index.tsx
|--states
|  |--atoms
|  |  |--inputAtom.ts
|--tyeps
|  |--inputFormType.ts

「pages」には、画面表示に関連するディレクトリになります。

「states/atoms」には、状態保持に関連するディレクトリになります。

「tyeps」には、状態保持の型定義に関連するディレクトリになります。

ディレクトリ構成は、例のためプロジェクトにあわせて適宜変更してください。

状態管理

「Recoil」を使用した状態管理するための処理例は、それぞれ次のようになります。

【tyeps/inputFormType.ts】

状態保持するためのデータ型を定義します。

type InputFormTyep = {
  id: string;
  name: string;
};
export default InputFormTyep;

【states/atoms/inputAtom.ts】

状態保持は「atom」で管理しています。データ型で定義した情報を使用して、オブジェクトを定義します。

import { atom } from "recoil";
import InputFormType from "../../tyeps/inputFormType";

export const inputState = atom<InputFormType>({
  key: "input",
  default: {
    id: null,
    name: ""
  }
});

「key」に指定する値は、ユニークとなるような値を指定する必要があります。

「default」には、初期値を設定することもできます。

詳しい使用方法については、公式サイトもあわせて確認してみてください。

atom(options) | Recoil
An atom represents state in Recoil. The atom() function returns a writeable RecoilState object.

画面

画面の処理例は、それぞれ次のようになります。

【pages/_app.tsx】

import type { AppProps } from "next/app";
import { RecoilRoot } from "recoil";

function App({ Component, pageProps }: AppProps) {
  return (
    <RecoilRoot>
      <div>Next.js + TypeScript</div>
      <Component {...pageProps} />
    </RecoilRoot>
  );
}

export default App;

状態管理をするためには「RecoilRoot」で括る必要があります。詳しい使用方法については、公式サイトもあわせて確認してみてください。

| Recoil
Provides the context in which atoms have values. Must be an ancestor of any component that uses any Recoil hooks.

【pages/index.tsx】

入力画面の作成します。

import type { NextPage } from "next";
import { useForm } from "react-hook-form";
import { useRecoilState } from "recoil";
import { inputState } from "../states/atoms/inputAtom";
import InputFormType from "../tyeps/inputFormType";
import Router from "next/router";

const Input: NextPage = () => {
  const [input, setInput] = useRecoilState(inputState);

  const { register, handleSubmit } = useForm<InputFormType>({
    defaultValues: {
      id: input.id,
      name: input.name
    }
  });

  const onSubmit = handleSubmit((data: InputFormType) => {
    setInput((currentInput) => ({
      ...currentInput,
      ...{
        id: data.id,
        name: data.name
      }
    }));
    Router.push("/confirm");
  });

  return (
    <div>
      <form onSubmit={onSubmit}>
        <div>
          <label>
            <span>id:</span>
            <input type="text" {...register("id")} />
          </label>
        </div>
        <div>
          <label>
            <span>name:</span>
            <input type="text" {...register("name")} />
          </label>
        </div>
        <div>
          <button type="submit">confirm</button>
        </div>
      </form>
    </div>
  );
};

export default Input;

「useRecoilState」に作成した「atom」を指定して使用します。

const [input, setInput] = useRecoilState(inputState);

処理例の指定方法の場合は、読み取り/書き込みの両方ができるようになります。詳しい使用方法については、公式サイトもあわせて確認してみてください。

useRecoilState(state) | Recoil
Returns a tuple where the first element is the value of state and the second element is a setter function that will upda...

「useForm」を使用してフォーム情報のオブジェクトを定義します。

const { register, handleSubmit } = useForm<InputFormType>({
  defaultValues: {
    id: input.id,
    name: input.name
  }
});

「defaultValues」には、「Recoil」から取得した情報を使用して初期値を設定しています。指定することで「確認画面」から戻ってきたときも値を保持しておくことが可能となります。

フォームのSubmitを実行するときの処理になります。入力した情報を「Recoil」に設定する処理を実施しています。

const onSubmit = handleSubmit((data: InputFormType) => {
  setInput((currentInput) => ({
    ...currentInput,
    ...{
      id: data.id,
      name: data.name
    }
  }));
  Router.push("/confirm");
});

値を設定後は、「確認画面」へ遷移する動作を実施しています。

【pages/confirm.tsx】

確認画面を作成します。

import type { NextPage } from "next";
import Link from "next/link";
import { useRecoilValue } from "recoil";
import { inputState } from "../states/atoms/inputAtom";

const Confirm: NextPage = () => {
  const input = useRecoilValue(inputState);

  return (
    <div>
      <form>
        <div>
          <label>
            <span>id:</span>
            <div>{input.id}</div>
          </label>
        </div>
        <div>
          <label>
            <span>name:</span>
            <div>{input.name}</div>
          </label>
        </div>
        <div>
          <Link href="/">Back Input</Link>
        </div>
      </form>
    </div>
  );
};

export default Confirm;

「useRecoilValue」に作成した「atom」を指定します。

const input = useRecoilValue(inputState);

処理例の指定方法の場合は、読み取りのみができるようになります。詳しい使用方法については、公式サイトもあわせて確認してみてください。

useRecoilValue(state) | Recoil
Returns the value of the given Recoil state.

まとめ

「Recoil」と「React Hook Form」を使用した実装方法の例を紹介しました。