27
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ReactでTodoアプリを作る(実装編)

Last updated at Posted at 2015-07-10

#前提
http://todomvc.com/examples/react/#/
が作りたい。react.jsのキャッチアップ用エントリー。
実装の流れがメインでreact.jsの解説はほとんどないので、必要に応じて本家のドキュメント参照して下さい。

ソースコード

開始時点のもの
git clonenpm installして開始します

完了(リファクタ前)

完了(リファクタ後)
リファクタ、データをlocalstrageへ保存、速度改善など(まだあげてない)

##準備
準備編。も参照のこと。主にgulp,package.jsのお話。

前回のエントリから続いてやる場合は、以下の対応を行ってください。

ベースとなるhtml,sassを作成

まずはお手本を参考に、静的なhtmlを返す実装と、sassファイルを作成する。これをベースにコンポーネント化をしていく。(お手本とは少しdom構成等異なります)

長いので、以下参照
src/js/app.js

src/sass/main.scss

こんな感じになるはず。

todo.png

実装

stateをつかってレンダリングを行う。

Reactでは、状態(state)の変更を検知して画面の描画を行う。状態(データ)を定義し、そのデータからDOMツリーを作成するように変更する。
まずはリスト部分からスタート。

  1. getInitialStateの実装
  2. renderでstateを利用してliタグの配列を生成して利用
app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -2,7 +2,33 @@
     var React = require('react/addons');
 
     var TodoApp = React.createClass({
+        getInitialState:function(){
+            return{
+                todos:[
+                    {id:'_1',status:0,label:"call to mom."},
+                    {id:'_2',status:1,label:"walk the dog"},
+                    {id:'_3',status:0,label:"buy groceries for dinner"}
+                ]
+            }
+        },
         render: function(){
+
+            var todoArray = this.state.todos.map(function(todo){
+                var completed = todo.status === 1;
+                return (
+                    <li className={completed ? 'completed todo-list-item' : "todo-list-item" }>
+                        <div className="todo-list-item-view-box">
+                            <input className="todo-list-item-check" type="checkbox" checked={completed}></input>
+                            <span className="todo-list-item-label">{todo.label}</span>
+                            <button className="todo-list-item-remove" type="button"></button>
+                        </div>
+                        <div className="todo-list-item-edit-box">
+                            <input type="text"></input>
+                        </div>
+                    </li>
+                );
+            });
+
             return (
                 <div className="container">
                     <header>
@@ -14,36 +40,7 @@
                             <input className="todo-input" type="text" placeholder="What needs to be done?"></input>
                         </div>
                         <ul className="todo-list">
-                            <li className="todo-list-item">
-                                <div className="todo-list-item-view-box">
-                                    <input className="todo-list-item-check" type="checkbox"></input>
-                                    <span className="todo-list-item-label">aiueo</span>
-                                    <button className="todo-list-item-remove" type="button"></button>
-                                </div>
-                                <div className="todo-list-item-edit-box">
-                                    <input type="text"></input>
-                                </div>
-                            </li>
-                            <li className="todo-list-item completed">
-                                <div className="todo-list-item-view-box">
-                                    <input className="todo-list-item-check" type="checkbox"></input>
-                                    <span className="todo-list-item-label">kakiku</span>
-                                    <button className="todo-list-item-remove" type="button"></button>
-                                </div>
-                                <div className="todo-list-item-edit-box">
-                                    <input type="text"></input>
-                                </div>
-                            </li>
-                            <li className="todo-list-item editing">
-                                <div className="todo-list-item-view-box">
-                                    <input className="todo-list-item-check" type="checkbox"></input>
-                                    <span className="todo-list-item-label">kakiku</span>
-                                    <button className="todo-list-item-remove" type="button"></button>
-                                </div>
-                                <div className="todo-list-item-edit-box">
-                                    <input type="text"></input>
-                                </div>
-                            </li>
+                            {todoArray}
                         </ul>
                         <footer>
                             <span>1 item left</span>

コンポーネントの作成

次に、リストを構成する項目を別コンポーネント(TodoItem)に切り出す。
TodoItemは、現時点ではrenderメソッドのみ。

app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -1,32 +1,39 @@
 (function(){
     var React = require('react/addons');
 
+    var TodoItem = React.createClass({
+        render: function(){
+            var todo = this.props.todo;
+            var completed = todo.status === 1;
+            return (
+                <li className={completed ? 'completed todo-list-item' : "todo-list-item" }>
+                    <div className="todo-list-item-view-box">
+                        <input className="todo-list-item-check" type="checkbox" checked={completed}></input>
+                        <span className="todo-list-item-label">{todo.label}</span>
+                        <button className="todo-list-item-remove" type="button"></button>
+                    </div>
+                    <div className="todo-list-item-edit-box">
+                        <input type="text"></input>
+                    </div>
+                </li>
+            );
+        }
+    });
+
     var TodoApp = React.createClass({
         getInitialState:function(){
             return{
                 todos:[
                     {id:'_1',status:0, label:"call to mom."},
                     {id:'_2',status:1, label:"walk the dog"},
                     {id:'_3',status:0, label:"buy groceries for dinner"}
                 ]
             }
         },
         render: function(){
 
             var todoArray = this.state.todos.map(function(todo){
-                var completed = todo.status === 1;
-                return (
-                    <li className={completed ? 'completed todo-list-item' : "todo-list-item" }>
-                        <div className="todo-list-item-view-box">
-                            <input className="todo-list-item-check" type="checkbox" checked={completed}></input>
-                            <span className="todo-list-item-label">{todo.label}</span>
-                            <button className="todo-list-item-remove" type="button"></button>
-                        </div>
-                        <div className="todo-list-item-edit-box">
-                            <input type="text"></input>
-                        </div>
-                    </li>
-                );
+                return <TodoItem key={todo.id} todo={todo}></TodoItem>
             });
 
             return (

イベント処理の実装

次にチェックボックスにチェックを入れた際の挙動を実装する。
チェックボックスにチェックを入れた時の挙動は、

  1. 対象todoのstatusを変更する。
  2. todoの完了がわかるように、打ち消し線を引く

ポイントは、状態(state)は親コンポーネントが持つが、イベントは子のコンポーネントから発火するため、発火したイベントを親に伝える必要がある事。
また、通常javascriptで実装する、イベントを取得してビューを書き換えるという考え方ではなく、
状態に即したビューの描画の定義(renderメソッド)を行い、イベント発火時に状態を上書きする。この二つが分離している事に気をつける。

すでにrenderメソッドは状態(state)に応じたビューを描画するようになっているので、stateを上書きするイベントの実装を行う

  1. 親コンポーネントに状態(state)を変更するメソッドを追加し、子コンポーネントのプロパティとして渡す
  2. onChangeイベントで、プロパティとして渡された関数を呼び、状態を変更する
app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -2,13 +2,16 @@
     var React = require('react/addons');
 
     var TodoItem = React.createClass({
+        handleChange:function(e){
+            this.props.completeItem(this.props.todo.id, e.target.checked);
+        },
         render: function(){
             var todo = this.props.todo;
             var completed = todo.status === 1;
             return (
                 <li className={completed ? 'completed todo-list-item' : "todo-list-item" }>
                     <div className="todo-list-item-view-box">
-                        <input className="todo-list-item-check" type="checkbox" checked={completed}></input>
+                        <input className="todo-list-item-check" type="checkbox" checked={completed} onChange={this.handleChange}></input>
                         <span className="todo-list-item-label">{todo.label}</span>
                         <button className="todo-list-item-remove" type="button"></button>
                     </div>

@@ -30,11 +34,22 @@
                 ]
             }
         },
+        completeItem:function(id,completed){
+            var newTodos = this.state.todos.map(function(todo, index){
+                if(todo.id === id){
+                    return React.addons.update(todo,{status: {$set : (completed ? 1 : 0) }});
+                }else{
+                    return todo;
+                }
+            });
+            this.setState({todos:newTodos});
+        },
         render: function(){
 
             var todoArray = this.state.todos.map(function(todo){
-                return <TodoItem key={todo.id} todo={todo}></TodoItem>
-            });
+                return <TodoItem key={todo.id} todo={todo} completeItem={this.completeItem}></TodoItem>
+            }.bind(this));
+
 
             return (
                 <div className="container">

新たなtodoの追加を行う

次に、新しいtodoが追加された場合の挙動を実装する。
Reactでは、仮想DOMと実際のDOMにずれがないように実装する必要がある。
しかし、新規todoの入力欄に文字を入力した場合、今の実装では仮想DOMと実際のDOMがずれてしまっている(inputのvalueが異なる。)
そのため、ここで行う実装としては、

  1. 入力を行った際の仮想DOMと実際のDOMがずれないよう、入力値をstateとして持ち、そのstateを利用するようrenderメソッドを修正する
  2. inputのonChangeイベントで、入力値のstateを変更する。
  3. inputのonKeyPressイベントで、enter押下時に、stateに新しいtodoを追加する。
app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -31,7 +31,8 @@
                     {id:'_1',status:0, label:"call to mom."},
                     {id:'_2',status:1, label:"walk the dog"},
                     {id:'_3',status:0, label:"buy groceries for dinner"}
-                ]
+                ],
+                newTodoLabel : ''
             }
         },
         completeItem:function(id,completed){
@@ -44,6 +45,29 @@
             });
             this.setState({todos:newTodos});
         },
+        generateId : function(){
+            var num = 4;
+            return function(){
+                return '_' + num++;
+            }
+        }(),
+        handleNewTodoKeyPress: function(e){
+            if(e.charCode == 13){
+                var newTodoLabel = this.state.newTodoLabel.trim();
+                if(newTodoLabel.length > 0){
+                    var newTodo = {id : this.generateId(),status:0,label:newTodoLabel};
+                    this.setState(
+                        {
+                            todos:React.addons.update(this.state.todos,{$push:[newTodo]}),
+                            newTodoLabel: ''
+                        }
+                    );
+                }
+            }
+        },
+        handleNewTodoChange: function(e){
+            this.setState({newTodoLabel:e.target.value});
+        },
         render: function(){
 
             var todoArray = this.state.todos.map(function(todo){
@@ -59,7 +83,7 @@
                     <section className="main-area">
                         <div className="todo-input-area">
                             <input className="check-all-todos" type="checkbox" ></input>
-                            <input className="todo-input" type="text" placeholder="What needs to be done?"></input>
+                            <input className="todo-input" value={this.state.newTodoLabel} type="text" placeholder="What needs to be done?" onChange={this.handleNewTodoChange} onKeyPress={this.handleNewTodoKeyPress}></input>
                         </div>
                         <ul className="todo-list">
                             {todoArray}

全チェックの実装

同様に、このチェックボックスの仮想DOMにも問題があるため、修正する。

  1. チェックの有無をstateで保持し、それを利用するようrenderを修正
  2. チェックボックスのonChangeイベントで、チェックの有無と、各todoのstatusを変更を行う
app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -32,7 +32,8 @@
                     {id:'_2',status:1, label:"walk the dog"},
                     {id:'_3',status:0, label:"buy groceries for dinner"}
                 ],
-                newTodoLabel : ''
+                newTodoLabel : '',
+                allChecked : false
             }
         },
         completeItem:function(id,completed){
@@ -68,6 +69,18 @@
         handleNewTodoChange: function(e){
             this.setState({newTodoLabel:e.target.value});
         },
+        handleAllCheckChange : function(e){
+            this.setState(
+                {
+                    todos:
+                        this.state.todos.map(function(todo){
+                            return React.addons.update(todo,{status:{$set: (e.target.checked ? 1  : 0)}});
+                        }),
+                    allChecked: e.target.checked
+                }
+
+            );
+        },
         render: function(){
 
             var todoArray = this.state.todos.map(function(todo){
@@ -82,7 +95,7 @@
                     </header>
                     <section className="main-area">
                         <div className="todo-input-area">
-                            <input className="check-all-todos" type="checkbox" ></input>
+                            <input className="check-all-todos" type="checkbox" checked={this.state.allChecked} onChange={this.handleAllCheckChange}></input>
                             <input className="todo-input" value={this.state.newTodoLabel} type="text" placeholder="What needs to be done?" onChange={this.handleNewTodoChange} onKeyPress={this.handleNewTodoKeyPress}></input>
                         </div>
                         <ul className="todo-list">

残todo数表示の実装

stateの状態にあわせて件数が表示されるよう修正。

app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -86,7 +86,9 @@
             var todoArray = this.state.todos.map(function(todo){
                 return <TodoItem key={todo.id} todo={todo} completeItem={this.completeItem}></TodoItem>
             }.bind(this));
-
+            var activeTodoCount = this.state.todos.filter(function(todo){
+                return todo.status === 0;
+            }).length;
 
             return (
                 <div className="container">
@@ -102,7 +104,7 @@
                             {todoArray}
                         </ul>
                         <footer>
-                            <span>1 item left</span>
+                            <span>{activeTodoCount} items left</span>
                             <ul className="filter-list">
                                 <li className="filter-list-item">
                                     <a href="#all" className="selected">All</a>

削除ボタンを実装

削除ボタンのonClickイベントから、親コンポーネントに処理を移譲して、該当のtodoを除いた一覧をstateにセットする。

app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -5,6 +5,9 @@
         handleChange:function(e){
             this.props.completeItem(this.props.todo.id, e.target.checked);
         },
+        handleRemoveClick:function(e){
+            this.props.removeItem(this.props.todo.id);
+        },
         render: function(){
             var todo = this.props.todo;
             var completed = todo.status === 1;
@@ -13,7 +16,7 @@
                     <div className="todo-list-item-view-box">
                         <input className="todo-list-item-check" type="checkbox" checked={completed} onChange={this.handleChange}></input>
                         <span className="todo-list-item-label">{todo.label}</span>
-                        <button className="todo-list-item-remove" type="button"></button>
+                        <button className="todo-list-item-remove" type="button" onClick={this.handleRemoveClick}></button>
                     </div>
                     <div className="todo-list-item-edit-box">
                         <input type="text"></input>
@@ -81,10 +84,17 @@
 
             );
         },
+        removeItem:function(id){
+            this.setState({
+                todos: this.state.todos.filter(function(todo){
+                    return todo.id !== id;
+                })
+            });
+        },
         render: function(){
 
             var todoArray = this.state.todos.map(function(todo){
-                return <TodoItem key={todo.id} todo={todo} completeItem={this.completeItem}></TodoItem>
+                return <TodoItem key={todo.id} todo={todo} completeItem={this.completeItem} removeItem={this.removeItem}></TodoItem>
             }.bind(this));
             var activeTodoCount = this.state.todos.filter(function(todo){
                 return todo.status === 0;

完了todo の削除を実装

ボタンのonClickイベントから、完了済みを除いたtodoの一覧をセットする

app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -91,6 +91,13 @@
                 })
             });
         },
+        handleClearCompletedClick:function(e){
+            this.setState({
+                todos: this.state.todos.filter(function(todo){
+                    return todo.status === 0;
+                })
+            });
+        },
         render: function(){
 
             var todoArray = this.state.todos.map(function(todo){
@@ -126,7 +133,7 @@
                                     <a href="#completed" >Completed</a>
                                 </li>
                             </ul>
-                            <button type="button" className="clear-completed">clear completed</button>
+                            <button type="button" className="clear-completed" onClick={this.handleClearCompletedClick}>clear completed</button>
                         </footer>
                     </section>
                 </div>

編集の実装

  1. 編集中を表す状態を追加し、その状態に応じて表示が変わるように修正
  2. 編集中文字列を状態に追加し、編集用inputタグに表示されるように修正
  3. onDoubleClickイベントにて、編集中のstateを変更
  4. onChangeイベントにて、編集中文字列のstateを変更
  5. onKeyDownイベントにて、enterキー押下で文字列と編集中のstateを変更(文字列が空の場合は削除)
  6. onBlurイベントも同様(複数のケースで呼ばれるので注意)
  7. onKeyPressイベントにて、escキー押下で編集中のstateを変更
  8. ダブルクリックされるとfocusをあてる

まずはstateとrenderの変更

app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -2,6 +2,11 @@
     var React = require('react/addons');
 
     var TodoItem = React.createClass({
+        getInitialState:function(){
+            return {
+                editingText: ''
+            }
+        },
         handleChange:function(e){
             this.props.completeItem(this.props.todo.id, e.target.checked);
         },
@@ -12,14 +17,17 @@
             var todo = this.props.todo;
             var completed = todo.status === 1;
             return (
-                <li className={completed ? 'completed todo-list-item' : "todo-list-item" }>
+                <li className={todo.editing? "editing todo-list-item": completed ? 'completed todo-list-item' :  "todo-list-item" }>
                     <div className="todo-list-item-view-box">
                         <input className="todo-list-item-check" type="checkbox" checked={completed} onChange={this.handleChange}></input>
                         <span className="todo-list-item-label">{todo.label}</span>
                         <button className="todo-list-item-remove" type="button" onClick={this.handleRemoveClick}></button>
                     </div>
                     <div className="todo-list-item-edit-box">
-                        <input type="text"></input>
+                        <input
+                            type="text"
+                            value={this.state.editingText}
+                            ></input>
                     </div>
                 </li>
             );
@@ -31,9 +39,9 @@
         getInitialState:function(){
             return{
                 todos:[
-                    {id:'_1',status:0, label:"call to mom."},
-                    {id:'_2',status:1, label:"walk the dog"},
-                    {id:'_3',status:0, label:"buy groceries for dinner"}
+                    {id:'_1',status:0, label:"call to mom.", editing:false},
+                    {id:'_2',status:1, label:"walk the dog", editing:false},
+                    {id:'_3',status:0, label:"buy groceries for dinner", editing:false}
                 ],
                 newTodoLabel : '',
                 allChecked : false

状態を変更する各イベントの実装

app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -13,12 +13,43 @@
         handleRemoveClick:function(e){
             this.props.removeItem(this.props.todo.id);
         },
+        handleDoubleClick:function(e){
+            this.setState({
+                editingText : this.props.todo.label
+            })
+            this.props.startEditItem(this.props.todo.id);
+        },
+        handleChangeEdit:function(e){
+            this.setState({
+                editingText:e.target.value
+            })
+        },
+        handleKeyDownEdit:function(e){
+            if(e.which === 13){
+                this.completeEditItem();
+            }else if(e.which === 27){
+                this.props.cancelEditItem(this.props.todo.id);
+            }
+        },
+        handleBlurEdit:function(e){
+            if(this.props.todo.editing){
+                this.completeEditItem();
+            }
+        },
+        completeEditItem:function(){
+            var val = this.state.editingText.trim();
+            if(val){
+                this.props.completeEditItem(this.props.todo.id, val);
+            }else{
+                this.props.removeItem(this.props.todo.id);
+            }
+        },
         render: function(){
             var todo = this.props.todo;
             var completed = todo.status === 1;
             return (
                 <li className={todo.editing? "editing todo-list-item": completed ? 'completed todo-list-item' :  "todo-list-item" }>
-                    <div className="todo-list-item-view-box">
+                    <div className="todo-list-item-view-box" onDoubleClick={this.handleDoubleClick}>
                         <input className="todo-list-item-check" type="checkbox" checked={completed} onChange={this.handleChange}></input>
                         <span className="todo-list-item-label">{todo.label}</span>
                         <button className="todo-list-item-remove" type="button" onClick={this.handleRemoveClick}></button>
@@ -27,6 +58,9 @@
                         <input
                             type="text"
                             value={this.state.editingText}
+                            onChange={this.handleChangeEdit}
+                            onKeyDown={this.handleKeyDownEdit}
+                            onBlur={this.handleBlurEdit}
                             ></input>
                     </div>
                 </li>
@@ -106,10 +140,52 @@
                 })
             });
         },
+        startEditItem:function(id){
+            this.setState({
+                todos: this.state.todos.map(function(todo){
+                    return  React.addons.update(todo, {editing:{$set:todo.id === id}});
+                })
+            });
+        },
+        completeEditItem:function(id,newValue){
+            if(id){
+                this.setState({
+                    todos: this.state.todos.map(function(todo){
+                        if(id === todo.id && todo.editing){
+                            return  React.addons.update(todo, {editing:{$set:false},label:{$set:newValue}});
+                        }else{
+                            return todo;
+                        }
+                    })
+                });
+            }
+        },
+        cancelEditItem:function(id){
+            this.setState({
+                todos: this.state.todos.map(function(todo){
+                    if(todo.id === id){
+                        return  React.addons.update(todo, {editing:{$set:false}});
+                    }else{
+                        return todo;
+                    }
+                })
+            });
+        },
         render: function(){
 
             var todoArray = this.state.todos.map(function(todo){
-                return <TodoItem key={todo.id} todo={todo} completeItem={this.completeItem} removeItem={this.removeItem}></TodoItem>
+                return (
+                    <TodoItem
+                        key={todo.id}
+                        todo={todo}
+                        completeItem={this.completeItem}
+                        removeItem={this.removeItem}
+                        startEditItem={this.startEditItem}
+                        completeEditItem={this.completeEditItem}
+                        cancelEditItem={this.cancelEditItem}
+                        >
+                    </TodoItem>
+                );
             }.bind(this));
             var activeTodoCount = this.state.todos.filter(function(todo){
                 return todo.status === 0;

最後に、ダブルクリク時にフォーカスを当てる処理

app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -44,6 +44,11 @@
                 this.props.removeItem(this.props.todo.id);
             }
         },
+        componentDidUpdate:function(prevProps){
+            if(!prevProps.todo.editng && this.props.todo.editing){
+                React.findDOMNode(this.refs.editItem).focus();
+            }
+        },
         render: function(){
             var todo = this.props.todo;
             var completed = todo.status === 1;
@@ -56,6 +61,7 @@
                     </div>
                     <div className="todo-list-item-edit-box">
                         <input
+                            ref="editItem"
                             type="text"
                             value={this.state.editingText}
                             onChange={this.handleChangeEdit}

todoのフィルタリング

未完了、完了、全てでフィルタリングする。onClickイベントで実装することもできるが、それぞれ固有のurlを持たせるため、aタグのリンクにする。
directorを利用してルーティングを行う。

npm install director --save
app.js
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -1,5 +1,6 @@
 (function(){
     var React = require('react/addons');
+    var Router = require('director').Router;
 
     var TodoItem = React.createClass({
         getInitialState:function(){
@@ -84,9 +85,18 @@
                     {id:'_3',status:0, label:"buy groceries for dinner", editing:false}
                 ],
                 newTodoLabel : '',
-                allChecked : false
+                allChecked : false,
+                filter:'all'
             }
         },
+        componentDidMount:function(){
+            Router({
+                '/':this.setState.bind(this,{filter:'all'}),
+                '/active':this.setState.bind(this,({filter:'active'})),
+                '/completed': this.setState.bind(this,({filter:'completed'}))
+            }).init('/');
+
+        },
         completeItem:function(id,completed){
             var newTodos = this.state.todos.map(function(todo, index){
                 if(todo.id === id){
@@ -179,7 +189,20 @@
         },
         render: function(){
 
-            var todoArray = this.state.todos.map(function(todo){
+            var todoArray = this.state.todos.filter(function(todo){
+
+                switch (this.state.filter){
+                    case 'all':
+                        return true;
+                    case 'active':
+                        return todo.status === 0;
+                    case 'completed':
+                        return todo.status === 1;
+                    default:
+                        return true;
+                }
+            }.bind(this))
+                .map(function(todo){
                 return (
                     <TodoItem
                         key={todo.id}
@@ -214,13 +237,13 @@
                             <span>{activeTodoCount} items left</span>
                             <ul className="filter-list">
                                 <li className="filter-list-item">
-                                    <a href="#all" className="selected">All</a>
+                                    <a href="#/" className={this.state.filter === 'all' && 'selected'}>All</a>
                                 </li>
                                 <li className="filter-list-item">
-                                    <a href="#active" >Active</a>
+                                    <a href="#/active" className={this.state.filter === 'active' && 'selected'}>Active</a>
                                 </li>
                                 <li className="filter-list-item">
-                                    <a href="#completed" >Completed</a>
+                                    <a href="#/completed" className={this.state.filter === 'completed' && 'selected'}>Completed</a>
                                 </li>
                             </ul>
                             <button type="button" className="clear-completed" onClick={this.handleClearCompletedClick}>clear completed</button>

#参考
react.addons.update
https://facebook.github.io/react/docs/update.html

director
https://www.npmjs.com/package/director

event
https://facebook.github.io/react/docs/events.html

27
24
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
27
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?