はじめてのReact #21「ToDoアプリを作る」 中編

前回に引き続き今回もToDoアプリを作っていきます。目標としてはフォームの使い勝手を向上させつつToDoの削除に対応するところまでとなります。

フォームを改良する

Submitイベントに対応

現状、登録フォームのテキストボックスに内容を入力しエンターキーを押した瞬間、再読込されてしまいます。これを防ぐには、formタグにonSubmitイベントを定義してあげればOK。今回はエンターキーを押すと確認用のダイアログを表示し、OKボタンが押されれば追加処理を行う形に変更しました。

src/todocreate.jsを以下のように変更します。

import React, { Component } from 'react';

class ToDoCreate extends Component {
  constructor(props){
    super(props);
    this.state = {
      newtodo: ""
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(e){
    this.setState({
      newtodo: e.target.value
    });
  }

  handleSubmit(e){
    let newtodo = this.state.newtodo;
    if(newtodo !== "" && window.confirm('本当に登録しますか?')){
      this.props.onClick(newtodo);
      this.setState({newtodo:""});
    }
    e.preventDefault();
  }

  render(){
    return(
      <form onSubmit={this.handleSubmit}>
        <input type="text" value={this.state.newtodo} onChange={this.handleChange}/>
        <button>追加</button>
      </form>
    );
  }
}

export default ToDoCreate;

注意すべき点としてはconfirm()ではなく、window.confirm()とする必要があるところでしょうか。

Validationを行う

もう一つの問題点として、テキストボックスが空でも登録されている点ですね。これも直して置きます。src/todocreate.jsのhandleSubmit()を以下のように変更しました。空文字列または2文字未満であれば何もしません。

  handleSubmit(e){
    e.preventDefault();

    let newtodo = this.state.newtodo;
    if(newtodo === "" || newtodo.length < 2){
      return(false);
    }

    if(newtodo !== "" && window.confirm('本当に登録しますか?')){
      this.props.onClick(newtodo);
      this.setState({newtodo:""});
    }
  }

テキストボックスにfocusする

ToDoを入力する際にわざわざテキストボックスをクリックするのも面倒なので、ページが読み込まれたと同時にfocusをしてあげます。src/todocreate.jsを以下のように変更します。

import ReactDOM from 'react-dom';  //追加

class ToDoCreate extends Component {
  // (snip)

  componentDidMount(){
    ReactDOM.findDOMNode(this.refs.newtodo).focus();
  }

  // (snip)

  render(){
    return(
      <form onSubmit={this.handleSubmit}>
        <input type="text" ref="newtodo" value={this.state.newtodo} onChange={this.handleChange}/>
        <button>追加</button>
      </form>
    );
  }
}

上記は変更した部分だけを抜き出しています。

まずReactDOMの機能を利用しますので新たにimportしました。 componentDidMount()はLifeCycleの機能。ComponentがDOMツリーに追加された瞬間に呼び出されます。ここで単純にfocus()メソッドを実行してあげればよいのですが、問題はどうやってテキストボックスを特定するかです。

今回はテキストボックスに<input type="text" ref="newtodo">といった形でref属性を追加しました。この属性をつけておくとReactから簡単に参照することができるようになります。この場合だとthis.ref.newtodoですね。

ReactDOM.findDOMNode()は最終的にDOMオブジェクトを返却しますので、これでテキストボックスを検索し、戻り値のDOMオブジェクトに対してfocus()を実行しているといます。

Lifecycleについては過去の記事を参照してください。 blog.katsubemakito.net

ToDoの「削除」に対応

todo.js

handleRemove()を追加し、render()へ渡しています。

最終的に「削除」ボタンを押すと handleRemove() が実行されるのですがこれまでとちょっと変わった引数の取り出し方をしていますが、これは後述します。またfilter()は配列内から必要な要素だけを取り出す動きをするJavaScriptのメソッドになります。

class ToDo extends Component {
  // (snip)

  handleRemove(e){
    let cur  = this.state.todo;
    let id   = Number( e.currentTarget.getAttribute("data-id") );
    let todo = cur.filter( i => i.id !== id );

    this.setState({
      todo: todo
    });
  }

  render() {
    return(
      <div>
        <ToDoCreate onClick={this.handleClick} />
        <ToDoList data={this.state.todo} remove={this.handleRemove}/>
      </div>
    );
  }
}

todolist.js

<ToDoItem>を生成する際に、ToDoComponentのhandleRemove()への参照をそのまま渡す処理を追加しています。

      return(
        <ul className="todolist">{
          data.map( i => <ToDoItem key={i.id} item={i} remove={this.props.remove} /> )
        }</ul>
      );

todoitem.js

タスクを表示すると同時に削除ボタンを追加しています。onClickでToDoComponentのhandleRemoveが呼ばれます。

    return(
      <li key={id}>
        {name} 
        <button type="button" data-id={id} onClick={this.props.remove}>✖</button>
      </li>
    );

ここではHTMLのカスタムデータ属性を利用しています。handleRemove()ではこのdata-id属性をe.currentTarget.getAttribute("data-id")として読み込んで利用します。これまでのサンプルと同様に、onClickでToDoItemComponent内のメソッドを呼び出し、そこでToDoComponentのhandleRemoveを呼び出す形でももちろん動作します。お好きな方でどうぞ。

実行結果

ここまでのソースコードは以下になります。 github.com

続く

次回に続きます。 blog.katsubemakito.net