Reactでtodoアプリを作ろう②:機能実装(Todoの追加)

前回は見た目を整えるところまで行ったので、

今回はTodoリストを追加するプログラムを実装していきます。

どのコンポーネントで何を管理するか

Reactのプログラムを書く上で大事なのがStateの管理場所とその内容だと思います。

今回は次のように設定します。

Appコンポーネント:全てのTodo(Addボタンで追加されていく)
Formコンポーネント:入力されたtitleとcontentの値
          (AddされるとAppのstateに追加され、空になる)


まずはFormコンポーネントの作成から始めます。

①Form:入力されたTitleとContentの値をstateで管理する

このアプリではHookを使ってStateを管理していきますので
まずは以下のように宣言します。

//Form.jsx

//useStateをimportする
import React, {useState} from 'react';

//titleとcontent各々で宣言
const[toDoTitle, setToDoTitle] = useState("");
const[toDoContent, setToDoContent] = useState("");
   (state変数名)  (state変更関数)   (()内は初期値)


宣言後に、値を変更したいタイミングでstate変更関数を呼び出し、
変更したい値を引数として渡すことで、stateが変更されます。


今回はinput・textareaに入力された値を取得し、stateへセットしたいので
input、textareaにonChange属性を追加し、state変更関数を呼び出します。


そのまま呼び出すことも可能ですが、
可読性やメンテナンス性を考えて関数化しています。

//Form.jsx

//state変更関数をさらに関数化
  const handleToDoTitleInputChange = (e) => {
    setToDoTitle(e.target.value);
  }

  const handleToDoContentInputChange = (e) => {
    setToDoContent(e.target.value);
  }

//onChange属性の追加
<input 
          type="text" 
          className="form-control shadow"
          onChange={handleToDoTitleInputChange}
          value={toDoTitle}
          />
<textarea className="form-control shadow"
          onChange={handleToDoContentInputChange}
          value={toDoContent}
        ></textarea>
Reactにおけるvalueの設定について

input要素のvalue属性には、初期値を設定することがこれまで多かったのですが、
Reactでは挙動が異なるため少し注意が必要です。
※少しハマりました....


フォーム要素はReact側にコントロールされるため、値を変更するにはstateの変更も必要になります。


value属性にuseStateの値を代入することで、ユーザーが編集できるようにします。


こちらの記事に詳しい記述があります。

参考:qiita.com


ここまでで、入力した値を取得しそれぞれを、
toDoTitle・toDoContentへ代入することができました。


次は、この値をAppコンポーネントのstateへセットしていきます。

③AddボタンでAppコンポーネントのstateを変更する


必要な作業は以下になります。


●Appコンポーネント
(1)useStateの宣言
(2)stateに値を追加する関数の作成(addToDoList関数)
(3)(2)で作成した関数をFormコンポーネントへpropsとして渡す


●Formコンポーネント
(1)Appコンポーネントへ値を渡す関数の作成(callAddToDoList関数)
(2)AddボタンにonClickイベントを設定し、(1)で作成した関数を呼び出す


上記内容をコードに起こすと....

//App.jsx

const App = () => {
//useStateの宣言
  const[toDoList, setToDoList] = useState([]);

//FormからTitleとContentを受け取りstateへ追加する関数
//それぞれのキーを「title」、「content」に設定
  const addToDoList = (Title, Content) => {
    setToDoList(toDoList.concat({"title": Title, "content": Content}))
  }

 return(
   //addToDoList関数をpropsとして渡す
          <Form addToDoList={addToDoList}/>
  );
//Form.jsx

//Appコンポーネントからpropsで渡ってきたaddToDoList関数
//にFormで管理しているtoDoTitleとtoDoContentを渡す
//※e.preventDefault();は再レンダリングをキャンセルしています
const callAddToDoList = (e) => {
    e.preventDefault();
    props.addToDoList(toDoTitle, toDoContent);
  }

//onClickイベントにcallAddToDoList関数を設定
<button className="btn btn-success"
          type="submit" 
          onClick={callAddToDoList}>
          Add
        </button>


toDoListに要素を追加する際に使用しているconcat関数については
こちらを参照ください。

developer.mozilla.org


これで、Addボタンをクリックすることで
AppのStateが更新されるようになりました。


④ToDoListコンポーネントでTodoを表示

最後に、Todoの表示です。

ユーザーが入力したtodoはAppコンポーネントのToDoListで
オブジェクト型で管理されています。


なので各要素を一つずつ表示させるために、mapでイテレートします。
このとき、Reactでは各要素それぞれに、ユニークなキーが必要になるので
第二引数に「index」を設定し、この値を、イテレートで生成される一番高い階層の要素に設定します。


参考:
reactjs.org


ToDoList内の値は、設定したキーであるtitleとcontentでアクセスできるので
表示したい場所で{item.title}、{item.content}を記述。


さらにlengthプロパティでToDoList要素数を取得して
タスクがいくつ残っているかも表示しています。

//ToDoList.jsx

const ToDoList = (props) => {
  const toDoListItems = props.toDoList.map(
    (item, index) => {
      return(
        <div key={index} className="card toDo-item">
          <div className="card-body">
            <h5 className="card-title">Title: {item.title}</h5>
            <p className="card-text">Content: {item.content}</p>
          </div>
        </div>
      );
    }
    );

  return(
    <div className="toDo-list">
  //タスクの数を表示
      <h1>Your Tasks: {props.toDoList.length}</h1>
      <div>
        {toDoListItems}
      </div>
    </div>
  );
}


これで入力したTodoが表示されるようになりました!

このような感じで動くはずです。

f:id:hnm-n-1029:20200723174158g:plain


今回は以上になります!


↓↓私の師匠、もりけんさんの武骨日記。問題集、要チェック

kenjimorita.jp