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-Reduxを使ってFormを作成する

Posted at

React (TypeScript) + Redux Toolkit を使った、シンプルなフォームサンプルを作ります。
例として「ユーザー情報フォーム(名前とメールアドレス)」をReduxで管理します。

ディレクトリ構成

src/
├─ app/
│  └─ store.ts
├─ features/
│  └─ user/
│     ├─ userSlice.ts
│     └─ UserForm.tsx
├─ App.tsx
└─ main.tsx

1. セットアップ

# プロジェクト作成
npx create-react-app redux-form-sample --template typescript

# Redux Toolkit と React-Redux を追加
npm install @reduxjs/toolkit react-redux

2.Redux ストア設定 (src/app/store.ts)

src/app/store.ts
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "@/features/user/userSlice";

export const store = configureStore({
    reducer:{
        user:userReducer,
    },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

TypeScript 5.x 以降の新しい挙動で、
"verbatimModuleSyntax": true tsconfig.jsonに設定されていると、
「型はimport typeでインポートしなければならない」ルールが強制されます。

src/app/hooks.ts
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "./store";

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

✅ ポイント

TypedUseSelectorHook は「」なのでimport typeで読み込む必要があります。

useDispatch useSelectorは「値(関数)」なので、通常の import です。

3. Slice作成 (src/features/user/userSlice.ts)

userSlice.ts
import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";

interface UserState {
  name: string;
  email: string;
}

const initialState: UserState = {
  name: "",
  email: "",
};

const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    setName: (state, action: PayloadAction<string>) => {
      state.name = action.payload;
    },
    setEmail: (state, action: PayloadAction<string>) => {
      state.email = action.payload;
    },
    resetUser: (state) => {
      state.name = "";
      state.email = "";
    },
  },
});

export const { setName, setEmail, resetUser } = userSlice.actions;
export default userSlice.reducer;
  1. フォームコンポーネント (src/features/user/UserForm.tsx)
UserForm.tsx
import React from "react";
import { useAppSelector, useAppDispatch } from "@/app/hooks"; // 型付きhooks
import { setName, setEmail, resetUser } from "./userSlice";

export const UserForm: React.FC = () => {
  const dispatch = useAppDispatch();
  const { name, email } = useAppSelector((state) => state.user); // ← OK: 型は自動で RootState として推論される

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    alert(`送信しました:\n名前: ${name}\nメール: ${email}`);
  };

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: 400, margin: "2rem auto" }} className="border border-gray-500 rounded px-4 py-4">
      <h2>ユーザー情報フォーム</h2>
      <div style={{ marginBottom: "1rem" }}>
        <label>名前:</label>
        <input
          type="text"
          value={name}
          onChange={(e) => dispatch(setName(e.target.value))}
          style={{ width: "100%", padding: "0.5rem" }}
          className="border border-gray-500 rounded px-4 py-4"
        />
      </div>
      <div style={{ marginBottom: "1rem" }}>
        <label>メール:</label>
        <input
          type="email"
          value={email}
          onChange={(e) => dispatch(setEmail(e.target.value))}
          style={{ width: "100%", padding: "0.5rem" }}
          className="border border-gray-500 rounded px-4 py-4"
        />
      </div>
      <button type="submit" style={{ marginRight: "1rem" }} className="bg bg-blue-500 rounded text-white font-bold px-4 py-4 mr-4 hover:bg-blue-700 cursor-pointer">
        送信
      </button>
      <button type="button" onClick={() => dispatch(resetUser())} className="bg bg-gray-500 rounded text-white font-bold px-4 py-4 mr-4 hover:bg-gray-700 cursor-pointer">
        リセット
      </button>
    </form>
  );
};

5. ルート設定 (src/App.tsx)

App.tsx
import { UserForm } from './features/user/UserForm';

const App = ()=>{
    return (
    <div className="flex justify-center items-center min-h-screen p-8 bg-gray-50">
      <UserForm />
    </div>
  )
}

export default App;


6. Redux Provider設定 (src/main.tsx)

main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { Provider } from 'react-redux';
import { store } from './app/store.ts';


createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>   
  </StrictMode>,
)

動作

入力した「名前」と「メール」はReduxの状態に保存される。

フォーム送信時にReduxの状態をalertで確認。

「リセット」ボタンでRedux状態を初期化。

トラブルシューティング

1.✅ ① verbatimModuleSyntax による型インポートエラー

エラー内容:
'TypedUseSelectorHook' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.

これはTypeScript 5.x以降の新しい挙動で、
"verbatimModuleSyntax": true tsconfig.jsonに設定されていると、
「型はimport typeでインポートしなければならない」ルールが強制されます。

🔧 修正方法

/src/app/hooks.ts のインポートを以下のように変更します 👇

hooks.ts
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "./store";

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
✅ ポイント

TypedUseSelectorHook は「型」なのでimport typeで読み込む必要があります。

useDispatch useSelectorは「値(関数)」なので、通常のimportです。

✅ ② ファイル名の大文字・小文字の不一致

エラー内容:
File name '.../App/hooks.ts' differs from already included file name '.../app/hooks.ts' only in casing.

これは、import { useAppSelector, useAppDispatch } from "@/App/hooks"; の部分で、
App フォルダ名を大文字で書いているために起きています。

Windows は大文字・小文字を区別しないのですが、TypeScript は区別します。

🔧 修正方法

UserForm.tsx のインポートを以下のように直します 👇

UserForm.tsx
import { useAppSelector, useAppDispatch } from "@/app/hooks";

または、もし実際に src/App/hooks.ts が存在しているなら、
app Appのどちらかに統一してください(一般的には全部小文字にします)。

✅ ③ state.user の型が unknown 扱いされる

エラー内容:
Property 'name' does not exist on type 'unknown'
Property 'email' does not exist on type 'unknown'

これは、Redux RootState 型がunknownと推論されている場合に起こります。
useAppSelector の型付けができていない、または store.ts RootState定義が不完全です。

🔧 修正方法(store.ts を確認)

store.ts がこのようになっているか確認してください 👇

store.ts
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "@/feature/user/userSlice";

export const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

// ✅ RootState と AppDispatch の型を明示的に定義
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

これで useAppSelector RootState型を正しく参照でき、
state.user.name state.user.email に型が付きます。

サイト

React-Reduxに関する記事

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?