以下のような実装にすると動くと思います。
typescript
1import React, {FC, useState, useCallback } from 'react';
2
3type Carrers = Readonly<{ name: string, address: string }>;
4
5type Props = {
6 addForm(): void;
7 data: Carrers;
8 index: number;
9 onChangeForm(name: string): void ;
10}
11
12const Form: FC<Props> = ({ addForm, data, index, onChangeForm }) {
13 const handleNameChange = useCallback(e => {
14 const value = e.target.value;
15 onChangeForm(index, value);
16 });
17
18 return (
19 <form>
20 <div>
21 <label>
22 Name:
23 <input value={data.name} onChange={handleNameChange} />
24 </label>
25 </div>
26 <button type="button" onClick={addForm}>
27 Add form
28 </button>
29 </form>
30 );
31}
32
33const initialObject: Carrers = {
34 name: "",
35 address: ""
36};
37
38const App: FC = () => {
39 const [state, setState] = useState([initialObject]);
40
41 const onChangeForm = useCallback((index: number, name: string) => {
42 setState(currentState => {
43 const currentData = currentState[index];
44 const newData: Carrers = { ...currentData, name };
45
46 const newState = currentState.slice();
47 newState[index] = newData;
48
49 return newState;
50 });
51 }, []);
52
53 const addForm = useCallback(() => {
54 setState([...state, initialObject]);
55 }, []);
56
57 return (
58 <div className="App">
59 <h1>フォーム</h1>
60 {state.map((data, index) => (
61 <Form
62 key={index}
63 addForm={addForm}
64 data={data}
65 index={index}
66 onChangeForm={onChangeForm}
67 />
68 ))}
69 <h1>(確認用)</h1>
70 {state.map((data, index) => (
71 <div key={index}>
72 <div>name: {data.name}</div>
73 <div>address: {data.address}</div>
74 </div>
75 ))}
76 </div>
77 );
78}
79
onChangeForm
関数内で state
を更新していますが、いくつか注目して欲しいところがあります。
まず、useCallback
を用いてレンダリング毎に同じ関数を用いるようにしています。
次に、setState
に対して関数を渡しその中で新しい newState
を生成しています。その関数は現在の値である currentState
を受け取っています。こうすることで useState
から返される state
を監視しなくて済み、useCallback
が常に同じ関数を返すようにできています。
そして、既存のオブジェクトを変更せずに newState
を生成しています。Reactでは破壊的な変更を加えると場合によっては正しく動かない場合があるので(shallow equalによる値のチェックをする時)、例に示したように非破壊的な操作をするのが良いです。