🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

1345閲覧

React Hooks を用いた Todo アプリにおいてステートの更新で正しくレンダリングされない

退会済みユーザー

退会済みユーザー

総合スコア0

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2021/01/21 18:15

前提

現在 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

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

Reactは、Object.isによるアルゴリズムを使用して、stateの更新を検知します。Object.isでは、中身が異なっていても、オブジェクト自体が同じなら同値とみなされます。

https://ja.reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/is

stateを更新したいならば、以前のものとは異なるオブジェクトを新しいstateとして設定してあげなければなりません。

次のように、setTodosに与える値を、新しい配列にすることで、stateは更新されるかと思います。

diff

1- setTodos(TodoStore.getAll()); 2 3+ const newTodos = TodoStore.getAll() 4+ setTodos([...newTodos]);

投稿2021/01/21 19:26

uraway_

総合スコア116

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/01/22 10:00

state の仕組みからご教授いただきありがとうございます。 ご提案いただいた方法で無事に解決いたしました。 稚拙なミスでしたが丁寧に解説していただき感謝しております。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問