LoginSignup
4
5

More than 3 years have passed since last update.

Reactでメモアプリを作る(React基礎講座9)

Posted at

はじめに

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

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

116d349e1defb23e1458f5359e3a85c6.gif

2a9d4baca00d88f7bf4ef1ab914e5438.gif

シリーズ

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

React開発で見かける配列処理系のメソッド map , filter について(React基礎講座8) - Qiita

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

Reactを使ってJSXの内容をレンダリングする(create-react-app)(React基礎講座1) - Qiita

メモアプリとは

仕様は以下のようなシンプルな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を作る
      • これら2つの機能がstateを変更させます
    • コンポーネント Form.jsList.js をimportする
      • これらも今回はクラスコンポーネントで実装しましょう
  • /components/Form.js
    • メモのフォーム部分の見た目を作る
  • /components/List.js
    • メモ一覧の見た目を作る

コンポーネントの配置

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

まずは、index.js

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して呼び出していることが分かります。

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;

続いて、FormListコンポーネントを作成していきます。

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;
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;

スクリーンショット 2019-08-19 00.05.46.png

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

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

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

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

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

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

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

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

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に乗って渡ってきたmemosmapで要素ごとに処理した結果を変数listコンポーネントに入れて、その下のrenderメソッドで{list}として受け取ります。

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

Form.jsを作成する

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

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関数を使って自分自身の値を更新させていることがわかります。

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

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の詳細は、以下の記事を見て見てください。

参考

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

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

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

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

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

ここまでできれば一旦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(スプレッド構文)は頻出シンタックスなのでぜひ覚えてください。

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

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

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

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;
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

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

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

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

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

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

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

冗長な部分をリファクタ

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

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

そして、

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

というエラー対応をします。具体的には、Listコンポーネント’にある

タグにidを追加します。
<li key={memo.id}>

こんな感じですね。

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

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;
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本格入門 ~モダンスタイルによる基礎から現場での応用まで | 山田 祥寛
4
5
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
4
5