前提
現在 React を勉強していて、簡単な Todo アプリを作っています。
flux 思想で設計しており、View で変更を検知して Action でデータを発行、それを Dispatch で処理して Store に反映させています。
発生している問題・エラーメッセージ
Action、Dispatch、Store までは上手く処理できたのですが、Store で変更された State を受け取った View がレンダリングしてくれません。
State は更新されるものの画面上のタスクが変化しないのです。
- Create ボタンを押すと、タスクが新規作成されて表示されるはず
- 入力エリアに 既存タスクの ID を入力して Delete ボタンを押すと対象のタスクが削除されるはず
ただ、Delete 用に設置している input に何らかを入力すると、それまで溜まっていた変更が一気に反映されます。
例) Create を3回クリック -> 反応なし -> input に入力 -> タスクが3つ作成される
該当のソースコード
TodoStore.ts (Store)
ts
1import { EventEmitter } from 'events'; 2import dispatcher from '../dispatcher'; 3 4class TodoStore extends EventEmitter { 5 todos: { id: number; text: string; complete: boolean }[]; 6 7 constructor() { 8 super(); 9 this.todos = [ 10 { 11 id: 113464613, 12 text: 'Go Shopping', 13 complete: false, 14 }, 15 { 16 id: 235684679, 17 text: 'Pay Water Bills', 18 complete: false, 19 }, 20 ]; 21 } 22 23 createTodo(text: string) { 24 const id = Date.now(); 25 26 this.todos.push({ 27 id, 28 text, 29 complete: false, 30 }); 31 32 this.emit('change'); 33 } 34 35 deleteTodo(deleteId: number) { 36 const deleteIndex = this.todos.findIndex(({ id }) => id == deleteId); 37 38 if (deleteIndex !== -1) { 39 this.todos.splice(deleteIndex, 1); 40 this.emit('change'); 41 } 42 } 43 44 receiveTodos(todos: { id: number; text: string; complete: boolean }[]) { 45 this.todos = todos; 46 this.emit('change'); 47 } 48 49 getAll() { 50 return this.todos; 51 } 52 53 handleActions(action: any) { 54 switch (action.type) { 55 case 'CREATE_TODO': { 56 this.createTodo(action.text); 57 break; 58 } 59 case 'DELETE_TODO': { 60 this.deleteTodo(action.id); 61 break; 62 } 63 case 'RECEIVE_TODOS': { 64 this.receiveTodos(action.todos); 65 break; 66 } 67 } 68 } 69} 70 71const todoStore = new TodoStore(); 72dispatcher.register(todoStore.handleActions.bind(todoStore)); 73 74export default todoStore; 75
Todos.tsx (View)
tsx
1import React, { ChangeEvent, FC, useEffect, useState } from 'react'; 2import * as TodoActions from '../actions/TodoActions'; 3import Todo from '../components/Todo'; 4import TodoStore from '../stores/TodoStore'; 5 6const Todos: FC = () => { 7 const [todos, setTodos] = useState(TodoStore.getAll()); 8 const [deleteId, setDeleteId] = useState(Number); 9 10 const TodoComponents = todos.map((todo) => { 11 return <Todo key={todo.id} {...todo} />; 12 }); 13 14 const marginStyle = { 15 marginRight: '10px', 16 }; 17 18 const getTodos = () => { 19 setTodos(TodoStore.getAll()); 20 console.log(todos); // ボタンをクリックすると Todos.tsx の todos ステートが更新されていました。 21 }; 22 23 const reloadTodos = () => { 24 TodoActions.reloadTodos(); 25 }; 26 27 const createTodo = () => { 28 TodoActions.createTodo('New Todo'); 29 }; 30 31 const deleteTodo = (id: number) => { 32 TodoActions.deleteTodo(id); 33 }; 34 35 const handleChange = (event: ChangeEvent<HTMLInputElement>) => { 36 if (!(event.target instanceof HTMLInputElement)) { 37 return; 38 } 39 const intValue = Number(event.target.value); 40 setDeleteId(intValue); 41 }; 42 43 useEffect(() => { 44 TodoStore.on('change', getTodos); 45 return () => { 46 TodoStore.removeListener('change', getTodos); 47 }; 48 }, []); 49 50 return ( 51 <div> 52 <button className="btn btn-default" onClick={() => reloadTodos()} style={marginStyle}> 53 Reload! 54 </button> 55 <button className="btn btn-default" onClick={() => createTodo()} style={marginStyle}> 56 Create! 57 </button> 58 <button className="btn btn-default" onClick={() => deleteTodo(deleteId)} style={marginStyle}> 59 Delete! 60 </button> 61 <input type="number" value={deleteId} onChange={(e) => handleChange(e)} style={marginStyle} /> 62 <h3>Todos</h3> 63 <ul>{TodoComponents}</ul> 64 </div> 65 ); 66}; 67 68export default Todos; 69
Todo.tsx (View)
tsx
1import React, { FC } from 'react'; 2 3type Props = { 4 id: number; 5 text: string; 6 complete: boolean; 7}; 8 9const Todo: FC<Props> = (props: Props) => { 10 const { complete, text, id } = props; 11 const icon = complete ? '\u2714' : '\u2716'; 12 const marginStyle = { 13 marginRight: '20px', 14 }; 15 16 return ( 17 <li> 18 <span style={marginStyle}>{id}</span> 19 <span style={marginStyle}>{text}</span> 20 <span>{icon}</span> 21 </li> 22 ); 23}; 24 25export default Todo; 26
試したこと
Todos.tsx の todos ステートを TodoComponents で処理して Todo.tsx に渡しているのですが、todos ステートの更新を TodoComponents が追えていないような気がしています。
useRef 等も使って解決を試みているのですが、勉強不足もあってなかなか進まないため質問させていただきました。
補足情報(FW/ツールのバージョンなど)
json
1{ 2 "dependencies": { 3 "@types/flux": "^3.1.9", 4 "@types/react": "^17.0.0", 5 "@types/react-dom": "^17.0.0", 6 "@types/react-router": "^5.1.9", 7 "@types/react-router-dom": "^5.1.7", 8 "flux": "^4.0.0", 9 "react": "^17.0.1", 10 "react-dom": "^17.0.1", 11 "react-router": "^5.2.0", 12 "react-router-dom": "^5.2.0" 13 }, 14 "devDependencies": { 15 "ts-jest": "^26.4.4", 16 "typescript": "^4.1.3" 17 } 18} 19
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2021/01/22 10:00