0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React で遷移元を判定してフォームの初期化を制御する方法

Posted at

はじめに

確認画面から戻るボタンを押した際に入力内容を保持したい、特定の画面からの遷移時だけフォームをクリアしたい。そんな要件を実現するために、遷移元(referer)を判定してフォームの初期化処理を制御する方法を紹介します。

要件

  1. 確認画面からの戻り時に入力内容を保持

    • 登録画面 → 確認画面 → 戻るボタン
    • この場合、フォームをクリアせずに入力内容を保持する
  2. 特定の遷移元からの遷移時に入力内容を保持

    • パラメータを引き継ぐ遷移元からの遷移時
    • フォームをクリアせずに、引き継がれたパラメータを反映する
  3. その他の遷移元からの遷移時はフォームをクリア

    • メニューから直接遷移した場合
    • 一覧画面から遷移した場合(特定の遷移元を除く)

実装方法

1. Location 管理モジュール

遷移元の URL 情報を Redux state で管理します。

// src/modules/location.js
export const initialState = {
  title: "",
  current: "", // 現在のURL
  referer: "", // 遷移元のURL
  state: {},
  params: {},
};

// 画面遷移時の処理
function* change(action) {
  const { location } = yield select();
  const {
    location: { pathname = "", state = {} },
  } = action.payload;

  const referer = location.current; // 遷移前のURLをrefererとして保存

  yield put(
    batchActions([
      actions.set("current", pathname), // 現在のURLを更新
      actions.set("referer", referer), // 遷移元のURLを保存
    ])
  );
}

2. 正規表現による遷移元判定

確認画面の URL パターンを正規表現で定義します。

// src/constants/urlPatterns.js
// 確認画面のURLパターンを定義
export const regexConfirmEntry = /^\/items\/entry\/[^/]+\/confirm$/;
export const regexConfirmCombined = /^\/items\/combined\/[^/]+\/confirm$/;
export const regexConfirmEdit = /^\/items\/edit\/confirm$/;

実装パターン

パターン 1: 確認画面からの戻りのみを判定

最もシンプルなパターンです。確認画面からの遷移時のみ初期化をスキップします。

import { useSelector } from "react-redux";
import { regexConfirmEdit } from "../constants/urlPatterns";

const EditForm = () => {
  const dispatch = useDispatch();
  const location = useSelector((state) => state.location);

  useEffect(() => {
    // 確認画面からの遷移の場合は初期化処理をスキップ
    const fromConfirm = regexConfirmEdit.test(location?.referer || "");
    if (fromConfirm) {
      return;
    }

    // 初期化処理
    if (isEmpty(formValues)) {
      dispatch(formActions.setInitialValues({}));
      dispatch(change("form_name", "field_name", defaultValue));
    }
  }, [location?.referer]);
};

パターン 2: 確認画面と特定の遷移元リストを判定

複数の条件を組み合わせて判定するパターンです。

import { regexConfirmEntry } from "../constants/urlPatterns";

const EntryForm = () => {
  const dispatch = useDispatch();
  const { location } = useSelector((state) => state);

  useEffect(() => {
    const shouldInitialize = () => {
      const { current, referer } = location;

      // フォームを保持する遷移元リスト
      const keepFormUrls = ["detail", "list", "search"];

      // 確認画面からの遷移をチェック
      const checkConfirm = !regexConfirmEntry.test(referer || "");

      // 現在のURLと遷移元のURLが対象遷移元リストに含まれていないかチェック
      const checkCurrent = !keepFormUrls.some((url) => current.includes(url));
      const checkReferer = !keepFormUrls.some((url) =>
        (referer || "").includes(url)
      );

      // すべての条件を満たす場合のみ初期化を実行
      return checkCurrent && checkReferer && checkConfirm;
    };

    if (shouldInitialize()) {
      // 初期化処理
      dispatch(formActions.setInitialValues({}));
    }
  }, [location?.referer, location?.current]);
};

パターン 3: 複数の条件を OR で判定

複数の条件のいずれかに該当する場合に初期化をスキップするパターンです。

import { regexConfirmCombined } from "../constants/urlPatterns";

const CombinedForm = () => {
  const dispatch = useDispatch();
  const { location } = useSelector((state) => state);
  const referer = location?.referer || "";

  useEffect(() => {
    // 確認画面からの遷移、または特定の一覧からの遷移の場合は初期化をスキップ
    const shouldSkipInit =
      regexConfirmCombined.test(referer) || referer.includes("schedule_list");

    if (!shouldSkipInit) {
      dispatch(formActions.setInitialValues({ user }));
    }

    // その他の初期値設定
    form.batch(() => {
      form.change("user_id", user?.id);
      form.change("user_name", user?.name);
    });
  }, [referer]);
};

注意点・ベストプラクティス

1. 正規表現は厳密に定義する

曖昧なパターンは誤判定の原因になります。

// ✅ 良い例: 厳密なパターン
export const regexConfirmEntry = /^\/items\/entry\/[^/]+\/confirm$/;

// ❌ 悪い例: 曖昧なパターン
export const regexConfirmEntry = /confirm/;

2. undefined チェックを忘れずに

location.refererundefinedの可能性があるため、オプショナルチェーンやデフォルト値を使用します。

// ✅ 良い例
const fromConfirm = regexConfirmEdit.test(location?.referer || "");

// ❌ 悪い例
const fromConfirm = regexConfirmEdit.test(location.referer);

3. useEffect の依存配列に含める

location?.refererを依存配列に含めることで、遷移元が変わった時に再評価されます。

// ✅ 良い例
useEffect(() => {
  // ...
}, [location?.referer]);

// ❌ 悪い例(遷移元が変わっても再評価されない)
useEffect(() => {
  // ...
}, []);

4. 早期リターンで可読性を向上

条件分岐で早期リターンを使用して、不要な処理を避けます。

// ✅ 良い例
useEffect(() => {
  const fromConfirm = regexConfirmEdit.test(location?.referer || "");
  if (fromConfirm) {
    return; // 早期リターンで初期化処理をスキップ
  }

  // 初期化処理
  // ...
}, [location?.referer]);

5. 複数条件は関数に切り出す

複数の条件を判定する場合は、可読性を考慮して関数に切り出します。

// ✅ 良い例
const shouldInitialize = () => {
  const { current, referer } = location;
  const keepFormUrls = ["detail", "list"];
  const checkConfirm = !regexConfirmEntry.test(referer || "");
  const checkCurrent = !keepFormUrls.some((url) => current.includes(url));
  const checkReferer = !keepFormUrls.some((url) =>
    (referer || "").includes(url)
  );
  return checkCurrent && checkReferer && checkConfirm;
};

if (shouldInitialize()) {
  // 初期化処理
}

まとめ

遷移元を判定してフォームの初期化を制御する方法を紹介しました。この仕組みにより:

  • 確認画面から戻った際に入力内容が保持される
  • 特定の画面からの遷移時のみフォームがクリアされる
  • ユーザビリティが向上する

要件に応じて 3 つのパターンを使い分けることで、柔軟に対応できます。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?