🐶

ReactRouterDom(Type Script)で遷移先にデータを渡す

2023/09/05に公開

前回、ReactHookFormを使ってフォームを作成しました。
結果表示ページに遷移し、入力内容を確認しようと思います。

使用している各バージョンは
react@18.2.0
react-hook-form@7.46.1
react-router-dom@6.15.0
typescript@4.9.5

React Router Domのインストール

ターミナル内でnpmを使ってインストールする

npm i react-router-dom

v5とv6

v5までは@typesもインストールしていましたが、v6から必要ないみたいです。
v5とv6の違いが気になる方はこちらを読み解きましょう。
https://reactrouter.com/en/6.15.0/upgrading/v5

RouterDomの使い方は後日のんびりと書きたいと思います。

Sampleアプリの作成

https://zenn.dev/ameyo/articles/41fd8b98bea22f
基本的には上記のアプリから起こしていきます。

interfaceの共通化

入力フォームから結果表示に渡すデータの型は共通である必要があるので、もともと入力フォーム用に作ってあったinterfaceを外に出します。

InputData.ts
// Formに入力したデータ保存用のInterface
export interface InputData {
    username: string;
    gender: string;
    month: string;
    date: string;
    message: string;
}

入力フォームの作成

App.tsxをページ振り分けのルーターだけの機能にするため、入力フォームのページを外に出します。

InputPage.tsx
import { useNavigate } from 'react-router-dom'
import { InputData } from './InputData';
import { useForm } from 'react-hook-form';
import { ErrorMessage } from '@hookform/error-message';

export function InputPage() {
    // Navigate関数
    const navigate = useNavigate();

    // 月リスト
    const monthList: number[] = [];
    for (let i = 1; i <= 12; i++) {
        monthList.push(i);
    }

    // 日リスト
    const dateList: number[] = [];
    for (let i = 1; i <= 31; i++) {
        dateList.push(i);
    }

    // 月の日にち最大値リスト
    let datesOfMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

    // useForm関数の設定
    const {
        register,
        handleSubmit,
        getValues,
        formState: { errors },
    } = useForm<InputData>({ mode: 'onChange', defaultValues: { gender: 'female' } });

    // Submitイベント
    const onSubmit = (data: InputData) => {
        navigate('/result', {
            state: data
        });
    }

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <h2>Input Form Test</h2>
            <div>
                <div>名前</div>
                <div>
                    <input type="text"
                        {...register("username", {
                            required: "名前を入力してください",
                        })} />
                </div>
                <ErrorMessage
                    errors={errors}
                    name="username"
                    render={({ message }) => <div>{message}</div>}
                />
            </div>

            <div>
                <div>性別</div>
                <div>
                    <input type="radio" id="gender-male" value="male"
                        {...register("gender", {
                            required: true,
                        })} />
                    <label htmlFor="gender-male"></label>
                    <input type="radio" id="gender-female" value="female"
                        {...register("gender", {
                            required: true,
                        })} />
                    <label htmlFor="gender-female"></label>
                </div>
            </div>

            <div>
                <div>誕生日</div>
                <div>
                    <select
                        {...register("month")}>
                        {monthList.map((item) => {
                            return (
                                <option key={item} value={item}>
                                    {item}</option>
                            );
                        })}
                    </select>
                    <select
                        {...register("date", {
                            validate: {
                                message: value => {
                                    const month = Number(getValues('month'));
                                    const date = Number(value);
                                    if (datesOfMonth[month - 1] < date) {
                                        return "正しい日付を入力してください";
                                    }
                                    return;
                                }
                            }
                        })}>
                        {dateList.map((item) => {
                            return (
                                <option key={item} value={item}>
                                    {item}</option>
                            );
                        })}
                    </select>
                </div>
                <ErrorMessage
                    errors={errors}
                    name="date"
                    render={({ message }) => <div>{message}</div>}
                />
            </div>

            <div>
                <div>その他(100文字以内)</div>
                <div>
                    <textarea
                        {...register("message", {
                            maxLength: { value: 100, message: "100文字以内で入力してください" }
                        })}
                    ></textarea>
                </div>
                <ErrorMessage
                    errors={errors}
                    name="message"
                    render={({ message }) => <div>{message}</div>}
                />
            </div>

            <button type="submit">送信</button>
        </form>
    );
}

useNavigateでデータを渡す

前回のフォーム作成では、onSubmitイベントでダイアログ表示を行っていました。
今回はonSubmitイベント内でuseNavigateを使ってページの遷移を行います。

import { useNavigate } from 'react-router-dom'
import { InputData } from './InputData';
const navigate = useNavigate();

引き渡すデータ型を定義するため、InputDataをインポートします。
ReactRouterDomからuseNavigateをインポートし、useNavigateを宣言してnavigateで使用できるようにします。

// Submitイベント
const onSubmit = (data: InputData) => {
    navigate('/result', {
        state: data
    });
}

navigateで画面遷移を行います。第一引数で遷移先を指定します。
第二引数の中の指定項目でstateに遷移先に渡したいデータを指定します。
今回はフォームの入力データを保存しているdata: InputDataを指定しました。

結果表示ページの作成

入力ページから送られてくるデータを受け取り、表示するためのページを作成します。

ResultPage.tsx
import { useLocation } from "react-router-dom";
import { InputData } from "./InputData";

export function ResultPage() {
    // 前のページの情報を取得する
    const location = useLocation();
    const data = location.state as InputData;

    const username = data.username;
    const gender = data.gender;
    const month = data.month;
    const date = data.date;
    const message = data.message;
    
    return (
        <div>
            <p>name: {username}</p>
            <p>gender: {gender}</p>
            <p>Birthday: {month}/{date}</p>
            <p>{message}</p>
        </div>
    );
}

useLocationでデータを受け取る

import { useLocation } from "react-router-dom";
import { InputData } from "./InputData";

遷移前のページからデータを受け取るため、useLocationもインポートします。
また、引き渡されるデータ型を知るため、こちらでもInputDataをインポートします。

// 前のページの情報を取得する
const location = useLocation();
const data = location.state as InputData;

最初にuseLocationを使用するために宣言します。
次に、遷移前のページから渡されてくるデータを読み取ります。この時、型をInputDataであると明示しておきます。
これにて、data変数内にInputData型のデータが格納されることになり、結果表示のページを作成できます。

Routerページを作成する

最後にInputPageとResultPageを分けるルーターをApp.tsx内に書き込んでいきます。

App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { InputPage } from './InputPage';
import { ResultPage } from './ResultPage';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route index element={<InputPage />} />
        <Route path={`result`} element={<ResultPage />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

URLに追記がなければインプットページ。resultが追加されれば結果ページに飛びます。
404ページは今回割愛しております。

結果


フォームに書いて送信すると結果ページに反映されています。

Discussion