Reactでメモアプリを作る（React基礎講座9）

# はじめに

今回は、Reactを使って、メモを追加・削除ができる簡単なメモアプリを作成していきます。

挙動は以下のような感じです。

![116d349e1defb23e1458f5359e3a85c6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/b366ed22-86d5-7912-6d32-85d3a65c0ccc.gif)

![2a9d4baca00d88f7bf4ef1ab914e5438.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/4f76a309-919e-87a2-b1f9-1053e748f519.gif)

# シリーズ

本記事はReact基礎講座のための連載になっています。気になる方のために、前の章は以下です。

[React開発で見かける配列処理系のメソッド map , filter について（React基礎講座8） \- Qiita](https://qiita.com/ryosuketter/items/c11a58242fa1ad9c6e4c)

最初の記事は、以下です。

[Reactを使ってJSXの内容をレンダリングする（create\-react\-app）（React基礎講座1） \- Qiita](https://qiita.com/ryosuketter/items/dbac89f398787a1a73d4)

# メモアプリとは

仕様は以下のようなシンプルなReactアプリケーションです。

- テキストエリアにテキストを入力して、入力ボタンを押したらその内容が一覧で表示される
- メモの一覧にはそれぞれ、削除ボタンがあり、それを押したらそのアプリが消える
- メモはDBには格納されない

# ファイル構成

```
root/
　├ public/
　└ src/
　 　└ components/
　　 |  └ Form.js
　　 |  └ List.js
　 　└ memoApp.js
　 　└ index.js

```

- index.js
    - `React.Component`である`memoApp`をimportする
- MemoApp.js
    - stateとして以下の情報を初期設定します
        - `memos`: 具体的なメモの情報
        - `nextId`: 次に追加するメモのidの情報
            - stateを持たせるので、クラスコンポーネントで実装します
    - メモを保存する機能 `addMemo` と、削除する機能 `deleteMemo`を作る
        - これら２つの機能が`state`を変更させます
    - コンポーネント `Form.js` と `List.js` をimportする
        - これらも今回はクラスコンポーネントで実装しましょう
- /components/Form.js
    - メモのフォーム部分の見た目を作る
- /components/List.js
    - メモ一覧の見た目を作る

# コンポーネントの配置

まずは、ファイル構成どおりにコンポーネントを配置していきましょう。

まずは、`index.js`

```react:index.js
import React from "react";
import { render } from "react-dom";

import MemoApp from "./MemoApp";

render(<MemoApp />, document.getElementById("root"));
```

次に、`MemoApp.js`

このコンポーネントから、`<Form />`と`<List />`をimportして呼び出していることが分かります。

```react:MemoApp.js
import React from "react";
import { render } from "react-dom";

import Form from "./components/Form";
import List from "./components/List";

class MemoApp extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
        <h2>MemoApp</h2>
        <Form />
        <List />
      </div>
    );
  }
}

export default MemoApp;
```

続いて、`Form`と`List`コンポーネントを作成していきます。

```react:Form.js
import React from "react";
import { render } from "react-dom";

class Form extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return <h3>Form</h3>;
  }
}

export default Form;
```

```react:List.js
import React from "react";
import { render } from "react-dom";

class List extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return <h3>List</h3>;
  }
}

export default List;
```

<img width="368" alt="スクリーンショット 2019-08-19 00.05.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/d2850be9-bfd9-4420-f1b9-e159c7b26ea0.png">

すると、こんな感じで作成できましたかね？これで、下準備というか、コンポーネントの配置は完了です。

# MemoAppにStateを定義してListで表示

MemoAppにStateを定義していきます。

前出しましたが、定義するStateは2種類です。

- `memos`: 具体的なメモの情報
    - 配列の中に連想配列を持たせましょう（keyは、`id`と`content`にしましょう）
    - これ、最終的にはテキストボックスからcontentを生成しますが、今回はコンポーネントの見た目確認のため、Stateに定義します
- `nextId`: 次に追加するメモのidの情報

`MemoApp`では、`<List />`コンポーネント部分にstateの`memos`を渡して、`List.js`で表示させるようにしましょう。

`List.js`では、`props`で、MemoAppにStateとして定義した`memos`を要素全て表示させましょう。
その時に、使用するメソッドは....mapでしたね。

それでは、実装していきましょう。まずは、`MemoApp.js`から。

```react:MemoApp.js
import React from "react";
import { render } from "react-dom";

import Form from "./components/Form";
import List from "./components/List";

class MemoApp extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      memos: [
        { id: 1, content: "one" },
        { id: 2, content: "two" },
        { id: 3, content: "three" },
        { id: 4, content: "four" },
        { id: 5, content: "five" }
      ],
      nextId: 0
    };
  }

  render() {
    return (
      <div>
        <h2>MemoApp</h2>
        <Form />
        <List memos={this.state.memos} />
      </div>
    );
  }
}

export default MemoApp;
```

こちらは、`State`を定義して、それを`State`として`<List />`コンポーネントに`memos`として渡します。

`List.js`では、`MemoApp.js`から`State`に乗って渡ってきた`memos`を`map`で要素ごとに処理した結果を変数`list`コンポーネントに入れて、その下のrenderメソッドで`{list}`として受け取ります。

```react:List.js
import React from "react";
import { render } from "react-dom";

class List extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const list = this.props.memos.map(memo => {
      return (
        <li>
          #{memo.id} - {memo.content}
        </li>
      );
    });
    return (
      <div>
        <h2>List</h2>
        {list}
      </div>
    );
  }
}

export default List;
```

こんな感じですね。見た目は、こんな感じになります。

![スクリーンショット 2019-08-19 22.56.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/116440e6-5ae3-c6f0-1161-2b54054f8ce4.png)


# Form.jsを作成する

今度は、メモを作成する`Form`のコンポーネントを作成します。

```react:Form.js
import React from "react";
import { render } from "react-dom";

class Form extends React.Component {
  constructor(props) {
    super(props);

    this.state = { content: "content" };
  }

  render() {
    return (
      <div>
        <h2>Form</h2>
        <input value={this.state.content} />
        <input type="submit" value="Add Memo" />
      </div>
    );
  }
}

export default Form;
```

ただ、このままだと、テキストエリアの値がいつまでも変わらないので、テキストエリア内の値が更新された場合、その変更後の値をテキストエリアに表示させます。テキストエリア内の値のstateが変更されたことを検知するイベントハンドラといえば、`onChange`ですね。これは、フォーム内の要素の内容が変更された時に起こるイベントハンドラです。

これをハンドラにして、メソッド`handleChange`を呼び出すような処理を書きましょう。`handleChange`関数の引数に`event`をようして、関数内で`event.target.value`と記述すると、イベントの結果更新された値を取得することができます。それを変数`content`として格納する。

その変数（`content`）を使って、`setState`してフォーム内の値（state）を更新します。つまり、`handleChange`関数を使って自分自身の値を更新させていることがわかります。

それでは、実装していきましょう。

```react:Form.js
import React from "react";
import { render } from "react-dom";

class Form extends React.Component {
  constructor(props) {
    super(props);

    this.state = { content: "content" };
  }

  render() {
    return (
      <div>
        <h2>Form</h2>
        <input value={this.state.content} onChange={this.handleChange} />
        <input type="submit" value="Add Memo" />
      </div>
    );
  }

  handleChange = event => {
    const content = event.target.value;
    this.setState({ content: content });
  };
}

export default Form;
```

次に、`submit`した際の挙動を書いていきましょう。`input`タグを`form`タグで囲って、`<input type="submit" value="Add Memo" />`をクリックした際の処理（関数を）`form`の開始タグに記述します。その際のハンドラは、`onSubmit`ですね。`onSubmit`の詳細は、以下の記事を見て見てください。

参考

https://www.sejuku.net/blog/28720

`onSubmit`で検知した際に、処理させる関数名は`handleSubmit`とでもしましょう。今回も引数`event`を受け取るようにしましょう。

処理内容は、一旦以下のようにします。

- まずは、submitをクリックした際のデフォルトの挙動をしないようにする
- 次に、アラートにて、現状の`this.state.content`の値を表示させるようにする
- アラートで、現状のcontentを表示させたら、現状のテキストエリアの値は何も無いことにします

こんな感じです。では、実装していきましょう。

```react:Form.js
import React from "react";
import { render } from "react-dom";

class Form extends React.Component {
  constructor(props) {
    super(props);

    this.state = { content: "content" };
  }

  render() {
    return (
      <div>
        <h2>Form</h2>
        <form onSubmit={this.hamdleSubmit}>
          <input value={this.state.content} onChange={this.handleChange} />
          <input type="submit" value="Add Memo" />
        </form>
      </div>
    );
  }

  handleChange = event => {
    const content = event.target.value;
    this.setState({ content: content });
  };

  hamdleSubmit = event => {
    event.preventDefault();
    alert(this.state.content);
    this.setState({ content: "" });
  };
}

export default Form;
```

こんな感じですね。挙動は、こんな感じになります。

![b044a7af7e18e4f869ff43618be34f3e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/37aaec82-52fe-031b-47c5-d223aacaa411.gif)

ここまでできれば一旦OKです。

なので、`Form.js`のコンストラク内のstateのcontentで定義している文字列は空白でもいいかもですね。

次は、いよいよ、アラートを出すのではなく、実際に、stateを変更して、`List`コンポーネントを変更するような機能を作成していきます。

# stateを変更する機能を作成する

今度は、テキストフォームにメモの内容を記述したら、`List`コンポーネントにその値が反映されるようにしましょう。

手順は以下のような感じです。

- （色々なやり方がありますが今回は）`MemoApp.js`でテキストを入力してボタンを押したら
    - 関数（`addMemo`）が走るようにしましょう
        - 関数（`addMemo`）自体は、
            - `setState`が走るようにして
            - その中でstate（memosの配列に`Spread operator`で既存のものを残しつつ、新たな要素を追加します）
            - contentの内容は`Form.js`内の`hamdleSubmit`関数の中で引数として渡します
            - 宣言元の`addMemo`は引数として`hamdleSubmit`関数から渡ってきたメモ内容（`this.state.content`）を引数（`content`）として受け取って、`Spread operator`（3点）の後にカンマして、あらたなオブジェクトに渡す
            - そして、idも`this.state.nextId`もセットして、その後の`setState`内で 1たす項目を設けます
- 既存のstate（`memos`）に`submit`した際、要素が加われば、コンストラクタで定義した既存のstate（`memos`）は消しましょう

JSでは、`Spread operator`（スプレッド構文）は頻出シンタックスなのでぜひ覚えてください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax

また、今回実装した`state`を変更するメソッド（`addMemo`）を親（`MemoApp.js`）に持たせて、それを子（`Form.js`）に渡して実行する方法はよくあるので難しいですが手に馴染ませてください。

子に渡っているのは`hamdleSubmit`関数の中の`this.props.addMemo(this.state.content);` ← この部分ですね。

それでは、実装していきましょう。

```react:MemoApp.js
import React from "react";
import { render } from "react-dom";

import Form from "./components/Form";
import List from "./components/List";

class MemoApp extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      memos: [],
      nextId: 0
    };
  }

  addMemo = content => {
    this.setState({
      memos: [...this.state.memos, { id: this.state.nextId, content: content }],
      nextId: this.state.nextId + 1
    });
  };

  render() {
    return (
      <div>
        <h2>MemoApp</h2>
        <Form addMemo={this.addMemo} />
        <List memos={this.state.memos} />
      </div>
    );
  }
}

export default MemoApp;
```

```react:Form.js
import React from "react";
import { render } from "react-dom";

class Form extends React.Component {
  constructor(props) {
    super(props);

    this.state = { content: "" };
  }

  render() {
    return (
      <div>
        <h2>Form</h2>
        <form onSubmit={this.hamdleSubmit}>
          <input value={this.state.content} onChange={this.handleChange} />
          <input type="submit" value="Add Memo" />
        </form>
      </div>
    );
  }

  handleChange = event => {
    const content = event.target.value;
    this.setState({ content: content });
  };

  hamdleSubmit = event => {
    event.preventDefault();
    this.props.addMemo(this.state.content);
    this.setState({ content: "" });
  };
}

export default Form;
```

こんな感じですね。挙動は、こんな感じになります。

![116d349e1defb23e1458f5359e3a85c6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/b366ed22-86d5-7912-6d32-85d3a65c0ccc.gif)

# stateを削除する機能を作成する

次は、個々のメモを削除するような機能を作成していきます。

手順は以下のような感じです。

- `List.js`のコンポーネントに<button>タグを設置
- 機能自体は`MemoApp.js`の`addMemo`の下に`deleteMemo`メソッドを作成していきましょう
    - `deleteMemo`メソッドは引数にmemosの`id`を取れるようにしてください
- では、上記の呼び出しを`List.js`に定義したbuttonタグにハンドラは`onClick`として、アロー関数を`deleteMemo`メソッドのコールバックとして実行してください
- コール先の関数の中の処理ですが
    - 配列（`memos`）を`filter`で処理して、引数として受け取ったid以外を`TRUE`として返すような条件でにして、結果を変数`filteredArray`に格納し、それをmemosの`setState`として渡す

それでは、実装していきましょう。

```react:MemoApp.js
import React from "react";
import { render } from "react-dom";

import Form from "./components/Form";
import List from "./components/List";

class MemoApp extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      memos: [],
      nextId: 0
    };
  }

  addMemo = content => {
    this.setState({
      memos: [...this.state.memos, { id: this.state.nextId, content: content }],
      nextId: this.state.nextId + 1
    });
  };

  deleteMemo = id => {
    const filteredArray = this.state.memos.filter(memo => {
      return memo.id !== id;
    });
    this.setState({ memos: filteredArray });
  };

  render() {
    return (
      <div>
        <h2>MemoApp</h2>
        <Form addMemo={this.addMemo} />
        <List memos={this.state.memos} deleteMemo={this.deleteMemo} />
      </div>
    );
  }
}

export default MemoApp;
```

```react.List.js
import React from "react";
import { render } from "react-dom";

class List extends React.Component {
  render() {
    const list = this.props.memos.map(memo => {
      return (
        <li>
          #{memo.id} - {memo.content}{" "}
          <button onClick={() => this.props.deleteMemo(memo.id)}>delete</button>
        </li>
      );
    });
    return (
      <div>
        <h2>List</h2>
        <ul>{list}</ul>
      </div>
    );
  }
}

export default List;
```

こんな感じですね。挙動は、こんな感じになります。

![2a9d4baca00d88f7bf4ef1ab914e5438.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/4f76a309-919e-87a2-b1f9-1053e748f519.gif)

# 冗長な部分をリファクタ

冗長な書き方になってしまっている部分を`const { ... } = this.state;`（`props`でも同様）と定義してリファクタしてあげましょう。

また、この記述`import { render } from "react-dom";`が不要なファイルはあるので、不要であれば消します。

そして、

```
Warning: Each child in a list should have a unique "key" prop.
```

というエラー対応をします。具体的には、`List`コンポーネント’にある<li>タグに`id`を追加します。

```js
<li key={memo.id}>
```

こんな感じですね。

それでは、実装していきましょう。

```react:MemoApp.js
import React from "react";

import Form from "./components/Form";
import List from "./components/List";

class MemoApp extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      memos: [],
      nextId: 0
    };
  }

  addMemo = content => {
    const { memos, nextId } = this.state;
    this.setState({
      memos: [...memos, { id: nextId, content: content }],
      nextId: this.state.nextId + 1
    });
  };

  deleteMemo = id => {
    const { memos } = this.state;
    const filteredArray = memos.filter(memo => {
      return memo.id !== id;
    });
    this.setState({ memos: filteredArray });
  };

  render() {
    const { memos } = this.state;
    return (
      <div>
        <h2>MemoApp</h2>
        <Form addMemo={this.addMemo} />
        <List memos={memos} deleteMemo={this.deleteMemo} />
      </div>
    );
  }
}

export default MemoApp;
```

```react:List.js
import React from "react";

class List extends React.Component {
  render() {
    const { memos, deleteMemo } = this.props;
    const list = memos.map(memo => {
      return (
        <li key={memo.id}>
          #{memo.id} - {memo.content}{" "}
          <button onClick={() => deleteMemo(memo.id)}>delete</button>
        </li>
      );
    });
    return (
      <div>
        <h2>List</h2>
        <ul>{list}</ul>
      </div>
    );
  }
}

export default List;
```

長くなりましたが、Reactを使ったメモアプリ作成は以上です。参考にしてみてください。

# 参考

- 改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで | 山田 祥寛