添削してみます。
Property.ts
- Property という型はユーザーを意味しているようなので、User にすると良いでしょう。
- RootState という型名は redux が扱う state 全体を表すので、ここでは使いません。
UsersState という名前にして、ユーザーの配列を持つようにすると良いでしょう。
- これらは redux が扱う state の型なので、pages/components の下じゃない方が良い気が…。
ts
1export interface User {
2 name: string;
3 sex: string;
4 age: number;
5 hobby: string;
6 index: number;
7}
8
9export type UsersState = User[];
stateSlice.ts → usersSlice.ts
- ユーザーの配列を扱う slice なので、usersSlice という名前にすると良いでしょう。
この slice が持つ state はユーザーの配列、つまり UsersState (User[]) になります。
- initialState に空文字列が入った要素を持たせるのは良くないですね。
空の配列にして、as UsersState で型を指定すると良いでしょう。
- アクション名は単なる add よりも addUser の方が分かりやすいような。
また、action の型は PayloadAction を使って action.payload の型を指定しましょう。
- addUser の引数 UserWithoutIndex は、User のうち index を含まないものです。
参考: 【TypeScript】型定義の指定したプロパティのみ利用したい(Pick, Omit)
- addUser では次の index を計算して、そのユーザーを追加した配列を返します。
- replaceUsers はユーザーの配列をまるごと入れ替えるものです。
ts
1import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2import { User, UsersState } from "../interface/Property";
3
4type UserWithoutIndex = Omit<User, "index">;
5
6export const usersSlice = createSlice({
7 name: "users",
8 initialState: [] as UsersState,
9 reducers: {
10 addUser: (state, action: PayloadAction<UserWithoutIndex>) => {
11 const index = Math.max(0, ...state.map(user => user.index)) + 1;
12 return [...state, { ...action.payload, index }];
13 },
14 replaceUsers: (state, action: PayloadAction<UsersState>) => {
15 return action.payload;
16 },
17 },
18});
19
20export const { addUser, replaceUsers } = usersSlice.actions;
21
22export default usersSlice.reducer;
store.ts
- stateManagement ではなく users にしましょう。
- RootState は redux が扱う state 全体の型、AppDispatch は dispatch の型になります。
ts
1import { configureStore } from "@reduxjs/toolkit";
2import usersSlice from "./usersSlice";
3
4export const store = configureStore({
5 reducer: {
6 users: usersSlice
7 },
8});
9
10export type RootState = ReturnType<typeof store.getState>;
11export type AppDispatch = typeof store.dispatch;
12
13export default store;
Add.tsx
- useState つまりこの画面に固有の state は、name, sex, age, hobby だけです。
- ユーザー追加の際、新しい index は reducer で計算するので、ここでは不要になります。
tsx
1import React, { useState } from "react";
2import { Layout } from "./Layout";
3import Header from "./Header";
4import { useDispatch } from "react-redux";
5import { AppDispatch } from "../../redux/store";
6import { addUser } from "../../redux/usersSlice";
7
8const Add = () => {
9 const dispatch = useDispatch<AppDispatch>();
10
11 const [name, setName] = useState("");
12 const [sex, setSex] = useState("");
13 const [age, setAge] = useState(0);
14 const [hobby, setHobby] = useState("");
15
16 const handleChangeName = (e: any) => {
17 setName(e.target.value);
18 };
19
20 const handleChangeSex = (e: any) => {
21 setSex(e.target.value);
22 };
23
24 const handleChangeAge = (e: any) => {
25 setAge(parseInt(e.target.value) ?? 0);
26 };
27
28 const handleChangeHobby = (e: any) => {
29 setHobby(e.target.value);
30 };
31
32 const onClickButton = () => {
33 dispatch(addUser({ name, sex, age, hobby }));
34 };
35
36 return (
37 // 略
38 <button
39 type="button"
40 onClick={onClickButton}
41 // 略
ListBody.tsx
- ユーザーの一覧を表示するだけなら、独自の state を持たないので、useState は不要です。
ts
1import Link from "next/link";
2import React from "react";
3import { useSelector, useDispatch } from "react-redux";
4import { RootState, AppDispatch } from "../../redux/store";
5import { replaceUsers } from "../../redux/usersSlice";
6
7const ListBody = () => {
8 const users = useSelector((state: RootState) => state.users);
9 const dispatch = useDispatch<AppDispatch>();
10
11 const dataInsert = () => {
12 const newUsers = [
13 {
14 name: "一郎",
15 sex: "男",
16 age: 22,
17 hobby: "珈琲",
18 index: 1,
19 },
20 {
21 name: "一子",
22 sex: "女",
23 age: 20,
24 hobby: "ピアノ",
25 index: 2,
26 }
27 ];
28 dispatch(replaceUsers(newUsers));
29 };
30
31 return (
32 // 略