ReactでシンプルなTodoアプリケーションを作成する

## はじめに

今更ながらReactを触ってみました。
以前Riot.jsを触っていたので、Reactはすごく書きやすいな〜という印象を持ちました。

簡単なTodoアプリケーションの作成をしながらReactを解説していきたいと思います。

> React歴2日目ですので、間違いなどありましたら、コメント頂けますと幸いです。

## 環境

> Nodeとcreate-react-appのインストール方法については記載しません。

* Mac OS X 10.14
* Nodejs 11.1.0
* create-react-app 2.1.1

## 1. アプリケーションを作成

```
$ create-react-app react-todo

$ cd react-todo
```

## 2. 不要ファイルの削除と初期ファイルの修正

`create-react-app`コマンドで生成されるディレクトリとファイル構造は以下

```
create-todo
┗node_modules/
┗public/
┗src/
  | App.css
  | App.js
  | App.test.js
  | index.css
  | index.js
  | logo.svg
  | serviceWorker.js
.gitignore
package.json
package-lock.json
README.md
```

上記のうち`src`ディレクトリ内の`App.js`と`index.js`、`serviceWorker.js`<b>以外を削除</b>します。

### componentsディレクトリの作成

そして、`src`ディレクトリ内に`components`ディレクトリを作成

```
// srcディレクトリ内にcomponentsディレクトリを作成
$ mkdir src/components

// App.jsをcomponentsディレクトリに移動
$ mv src/App.js src/components
```

### ファイル名を変更

以下ファイルの拡張子を`jsx`にします。特に変更する必要もないのですが、なんとなく変更しておきます。

* index.js -> index.jsx
* App.js -> App.jsx

### ファイルの修正

#### `index.jsx`を以下のように修正

`src/index.jsx`

```js
import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';

import App from './components/App';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
```

#### `App.jsx`を以下のように修正

`src/components/App.jsx`

```js
import React, { Component } from 'react';

export default class App extends Component {
  render() {
    return (
      <div className="App">
      </div>
    );
  }
}
```

#### index.htmlにsiimpleを追加

デザインを[Siimple](https://siimple.juanes.xyz/)に丸投げするので、CDNで読み込み

`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/siimple@3.1.1/dist/siimple.min.css">`を追記しています。

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <!-- 以下を追記 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/siimple@3.1.1/dist/siimple.min.css">
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>
```

## 3. Todoアプリケーションの概要

作成するTodoアプリケーションは以下のコンポーネントで構成されます。

* Appコンポーネント(stateを管理)
* Formコンポーネント(stateless)
* Listコンポーネント(stateless)

親コンポーネントがデータ（state）を管理し、子コンポーネントはstatelessで組みたいと思います。

親子関係は以下

```
App（親）
┗Form（子）
┗List（子）
```

## 4. Formコンポーネントを作成する

`functional component`で作成する。

> `class component`と`functional component`についてはとてもわかりやすいこの[記事](https://mae.chab.in/archives/2956#post2956-2)を参照ください。

`src/components`ディレクトリ内に`Form.jsx`を作成し、以下を入力。
まずは見た目だけを作っていきます。

```js
import React, { Component } from 'react';

const Form = (props) => (
  <form className="siimple-form">
    <div className="siimple-form-field">
      <label className="siimple-label siimple--color-white">Your todo:</label>
      <input name="title" type="text" className="siimple-input"/>　<input type="submit" value="Add" className="siimple-btn siimple-btn--teal"/>
    </div>
  </form>
);

export default Form;
```

作成しただけではまだ表示出来ないので、`App.jsx`に`Form.jsx`をimportさせます。

`src/components/App.jsx`を以下のように修正

```js
import React, { Component } from 'react';
import Form from './Form';

export default class App extends Component {
  render() {
    return (
      <div className="siimple-box siimple--bg-dark">
        <h1 className="siimple-box-title siimple--color-white">React Todo App</h1>
        <Form/>
      </div>
    );
  }
}
```

ここまで終わったら、

アプリケーションのルートディレクトリに移動して、`npm start`を実行[http://localhost:3000](http://localhost:3000)にアクセスすると開発中のページが見えるかと思います。

今の所以下のように表示されるかと思います。

![スクリーンショット 2018-11-05 23.58.52.png](https://qiita-image-store.s3.amazonaws.com/0/19716/34abd538-bd35-c1f3-be86-c8adc848133e.png)

## 5. List.jsxを作成する

`src/components`内に`List.jsx`を作成し、以下を入力します。

```js
import React, { Component } from 'react';

let style = {
  maxWidth: '700px',
};

let btn = {
  cursor: 'pointer'
};

const List = (props) => (
  <ul className="siimple-list">
    <li className="siimple-list-item siimple--bg-white" style={style}>Item 1 <span className="siimple-tag siimple-tag--error siimple-hover" style={btn}>Delete</span></li>
    <li className="siimple-list-item siimple--bg-white" style={style}>Item 2 <span className="siimple-tag siimple-tag--error siimple-hover" style={btn}>Delete</span></li>
    <li className="siimple-list-item siimple--bg-white" style={style}>Item 3 <span className="siimple-tag siimple-tag--error siimple-hover" style={btn}>Delete</span></li>
  </ul>
);

export default List;
```

上記入力後、再度`App.jsx`を以下のように修正します。

```js
import React, { Component } from 'react';
import Form from './Form';
import List from  './List';

export default class App extends Component {
  render() {
    return (
      <div className="siimple-box siimple--bg-dark">
        <h1 className="siimple-box-title siimple--color-white">React Todo App</h1>
        <Form/>
        <div className="siimple-rule"></div>
        <List/>
      </div>
    );
  }
}
```

## 6. 投稿機能を作成していく

あらかた見た目が出来たところで、機能を追加していく。

投稿機能では、親コンポーネント（App）と子コンポーネント（Form）で以下のように役割を分担する

* App
  * データ(state)を管理
  * データ保存用関数の定義
  * 子コンポーネントへデータ保存用関数を受け渡し
* Form
  * 親コンポーネントの関数をpropsで受け取り実行

### App.jsx

状態管理(state)は親コンポーネントに行わせるようにするので、`src/components/App.jsx`を修正。

```js
import React, { Component } from 'react';
import Form from './Form';
import List from  './List';

export default class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      todo: []
    };
    this.handleAdd = this.handleAdd.bind(this);
  }

  // データ保存
  handleAdd(e){
    console.log(e.target.title.value);
    e.preventDefault(); //これを書かないとリダイレクトされてしまう
  }
  render() {
    return (
      <div className="siimple-box siimple--bg-dark">
        <h1 className="siimple-box-title siimple--color-white">React Todo App</h1>
        <Form handleAdd={this.handleAdd}/>
        <div className="siimple-rule"></div>
        <List/>
      </div>
    );
  }
}
```

`constructor`とデータ保存を行う関数`handleAdd`を定義。

`this.handleAdd = this.handleAdd.bind(this);`この部分ですが、定義した`handleAdd`関数内でthisを使用出来るようにする為に`constructor`　内に定義しています。

`handleAdd(e)`として定義されていれば、受け取った`e`を使って`e.target.子コンポーネントで定義したフォーム要素のname属性.value`で値を取得出来る。

### Form.jsx

#### inputに入力したデータをconsole.logに出力

`src/components/Form.jsx`を修正
`form`タグにイベントハンドラ`onSubmit`を追記する。

```js
import React, { Component } from 'react';

const Form = (props) => (
  <form className="siimple-form" onSubmit={props.handleAdd}>
    <div className="siimple-form-field">
      <label className="siimple-label siimple--color-white">Your todo:</label>
      <input name="title" type="text" className="siimple-input"/>　<input type="submit" value="Add" className="siimple-btn siimple-btn--teal"/>
    </div>
  </form>
);

export default Form;
```

ここまでで、`npm start`をやり直して、`Your todo`に値を入れて、`Add`ボタンを押し、コンソールログを見てみましょう。

![スクリーンショット 2018-11-06 1.01.01.png](https://qiita-image-store.s3.amazonaws.com/0/19716/e758c6a5-6c8a-0526-7219-e16b175798b2.png)

以上のようにデータが取得出来ているかと思います。

#### データ保存処理を書く

console.logでフォームからデータが渡ってきていることを確認したら、保存処理を書いていきます。
`src/components/App.jsx`の`handleAdd`関数を以下のように書き換えます。


```js
// データ保存
handleAdd(e){
  // リダイレクト防止
  e.preventDefault();
  // フォームから受け取ったデータをオブジェクトに挿入して、stateのtodo配列に追加
  this.state.todo.push({title: e.target.title.value}); // まだ保存されていない
  // setStateを使ってstateを上書き
  this.setState({todo: this.state.todo}); // 保存完了
  // inputのvalueを空に
  e.target.title.value = '';
}
```

投稿機能の完成です。
しかし、まだ投稿したデータが表示されていないので、`List`を編集していきます。

## 7. 保存されたデータを表示する

保存されたデータ(state)を表示するにはまず`src/components/App.jsx`を編集していきます。

### App.jsx

```js
import React, { Component } from 'react';
import Form from './Form';
import List from  './List';

export default class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      todo: []
    };
    this.handleAdd = this.handleAdd.bind(this);
  }

  // データ保存
  handleAdd(e){
    e.preventDefault();
    // フォームから受け取ったデータをオブジェクトに挿入して、stateのtodo配列に追加
    this.state.todo.push({title: e.target.title.value});
    // setStateを使ってstateを上書き
    this.setState({todo: this.state.todo});
    // inputのvalueを空に
    e.target.title.value = '';
  }
  render() {
    return (
      <div className="siimple-box siimple--bg-dark">
        <h1 className="siimple-box-title siimple--color-white">React Todo App</h1>
        <Form handleAdd={this.handleAdd}/>
        <div className="siimple-rule"></div>
        <List todos={this.state.todo}/>
      </div>
    );
  }
}
```

`<List todos={this.state.todo}/>`のように変更し、stateのtodoを子コンポーネントへ`todos`という変数名で受け渡します。


### List.jsx

`src/components/List.jsx`を以下のように編集


```js
import React, { Component } from 'react';

let style = { maxWidth: '700px' };
let btn = { cursor: 'pointer' };

const List = (props) => (
  <ul className="siimple-list">
    {props.todos.map((todo, i) => {
      return <li key={i} className="siimple-list-item siimple--bg-white" style={style}>{todo.title} <span className="siimple-tag siimple-tag--error siimple-hover" style={btn}>Delete</span></li>
    })};
  </ul>
);

export default List;
```

親コンポーネント(App.jsx)でtodosを渡したので、子コンポーネント(List.jsx)でpropsを使い受け取り、ループを回しています。

この際にReactのルールでループで処理した要素には一意なKeyを指定する必要があります。なので、`key={i}`としています。

## 8. 削除処理

投稿機能、表示機能まで作成したので、最後に削除機能を作成していきましょう。

### App.jsx

`src/components/App.jsx`に`handleRemove`関数を作成し、`List`コンポーネントへ受け渡す。

```js
import React, { Component } from 'react';
import Form from './Form';
import List from  './List';

export default class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      todo: []
    };
    this.handleAdd = this.handleAdd.bind(this);
    this.handleRemove = this.handleRemove.bind(this);
  }

  // データ保存
  handleAdd(e){
    e.preventDefault();
    // フォームから受け取ったデータをオブジェクトに挿入して、stateのtodo配列に追加
    this.state.todo.push({title: e.target.title.value});
    // setStateを使ってstateを上書き
    this.setState({todo: this.state.todo});
    // inputのvalueを空に
    e.target.title.value = '';
  }

  // データ削除
  handleRemove(i){
    // todo配列からi番目から1つ目のデータを除外
    this.state.todo.splice(i,1);
    // setStateでtodo配列を上書き
    this.setState({todo: this.state.todo});
  }

  render() {
    return (
      <div className="siimple-box siimple--bg-dark">
        <h1 className="siimple-box-title siimple--color-white">React Todo App</h1>
        <Form handleAdd={this.handleAdd}/>
        <div className="siimple-rule"></div>
        <List todos={this.state.todo} handleRemove={this.handleRemove}/>
      </div>
    );
  }
}

```

### List.jsx

```js
import React from 'react';

let style = { maxWidth: '700px' };
let btn = { cursor: 'pointer' };

const List = (props) => (
  <ul className="siimple-list">
    {props.todos.map((todo, i) => {
      return <li key={i} className="siimple-list-item siimple--bg-white" style={style}>{todo.title} <span className="siimple-tag siimple-tag--error siimple-hover" style={btn} onClick={() => props.handleRemove(i)}>Delete</span></li>
    })};
  </ul>
);

export default List;
```

`<span className="siimple-tag siimple-tag--error siimple-hover" style={btn} onClick={() => props.handleRemove(i)}>Delete</span>`にonClickでpropsで親コンポーネントから受け取った`handleRemove`関数を実行させるようにしています。その際に、無名関数で実行し、iを引数に持たせています。

## 9. 完成形

![スクリーンショット 2018-11-06 1.57.58.png](https://qiita-image-store.s3.amazonaws.com/0/19716/8e45be21-e008-32b0-99dc-f4b9e412c5b2.png)

[ソースコード](https://github.com/ken-sayama/react-todo)も貼っておきます

以上で、簡単なReactのTodoアプリケーションが作成出来たかと思います。