4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

create-react-appでTodoアプリを作成

Posted at

はじめに

create-react-appはReactで簡単にアプリを作成するためのツールです。
Reactのみで簡単なTodoアプリを作成したので備忘録としてまとめたいと思います。
なお、多少の環境構築が必要なので、よろしければこの記事を参考にしてください。

また今回はJSXpropsuseStateの知識を扱います。この記事では説明を省きますので
ご了承ください。

挙動イメージ

ezgif.com-gif-maker (10).gif

  • 1. Todoを入力したら未完了のTodoに反映される
  • 2. 未完了のTodoは完了のTodoに移動できる or 削除できる
  • 3. 完了のTodoは未完了のTodoに移動できる

目次

1.create-react-app
2.HTMLとCSSで構造を作成
3.Reactを意識したモックに変更
4.Todoの入力機能
5.Todoの追加機能
6.Todoの削除機能
7.Todoの完了機能
8.Todoの戻す機能
9.コンポーネント化

1. create-react-app

環境構築が終了したらターミナルでcreate-react-appを実行しましょう。

ターミナル
projects $ npx create-react-app 任意のフォルダ名今回はreact-todo
projects $ cd react-todo

// サーバー立ち上げ
react-todo $ npm start

ブラウザが立ち上がってReactの初期画面が表示されれば成功です!

2. HTMLとCSSで構造を作成

まずは、イメージの通りに骨組みを作成します。
srcディレクトリの必要なファイルは以下通りです。
App.jsはコンポーネントと明示的にわかるようにApp.jsxと拡張子を変更しましょう。

src
|-App.jsx
|-index.js
|-App.css

次にindex.jsを編集します。

index.js
import React from 'react';
import ReactDom  from 'react-dom';
import { App } from './App';


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

上から2行はおまじないです。
3行目はApp.jsxのAppという関数をimportしますという意味です。
最後にrender関数でAppの中身をindex.htmlのidがrootのdivタグに格納しています。

準備が整ったのでメインのApp.jsxを編集します。

App.jsx
import React from 'react';
// App.cssを読み込み
import './App.css';

export const App = () => {
  return (
    <>
      <div className="outline">
        <div className="inputArea">
          <input placeholder="todoを入力"/>
          <button>追加</button>
        </div>
        <div className="imcompleteArea">
          <p className="title">未完了のtodo</p>
          <ul>
            <li className="todoList">
              <p>テスト</p>
              <button>完了</button>
              <button>削除</button>
            </li>
          </ul>
        </div>
        <div className="completeArea">
        <p className="title2">完了のtodo</p>
          <ul>
            <li className="todoList">
              <p>テスト</p>
              <button>戻す</button>
            </li>
          </ul>
        </div>
      </div>
    </>
  );
};

構成としては大きくinputAreaimcompleteAreacompleteAreaに分かれます。
Todoが格納させるエリアにはリストタグで個々のtodoにボタンで操作できるようにしておきます。

ポイント

  • return内が複数行になるときは<> </>で全体をくくる
  • JSXでclassを付与するときはclassName記述する

次にcssですが、ここはお好みで調整可能です。
記述方法は通常のcssとかわりません。

App.css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

li {
  list-style: none;
}

.outline {
  padding: 50px;
}

.inputArea {
  width: 300px;
  height: 70px;
  background-color: lightblue;
  padding: 20px;
  margin-bottom: 50px;
  border-radius: 15px;
}
input {
  border-radius: 16px;
  border: none;
  outline: none;
  padding: 6px 16px;
  margin-right: 10px;
}

button {
  border-radius: 16px;
  border: none;
  padding: 4px 16px;
}
button:hover {
  background-color: #FF7FFF;
  color: #FFF;
  cursor: pointer;
}

/* 未完了のタスクエリア */
.imcompleteArea {
  width: 800px; 
  min-height: 300px;
  background-color: lightgray;
  padding: 20px;
  margin-bottom: 50px;
  border-radius: 15px;
}
.title {
  text-align: center;
  font-weight: bold;
}
.todoList {
  margin-top: 20px;
  display: flex;
  align-items: center;
  justify-content: space-evenly;
}

/* 完了のタスクエリア */
.completeArea {
  width: 800px;
  min-height: 300px;
  background-color: lightgreen;
  padding: 20px;
  border-radius: 15px;

}
.title2 {
  text-align: center;
  font-weight: bold;  
}

以上で骨組みはできました。

3. Reactを意識したモックに変更

現状のApp.jsx内では未完了と完了のtodoはpタグに直接書き込まれています。
しかし、本来は動的にtodoの内容は反映してほしいのでuseStateを用いて状態を管理していきたいと思います。

App.jsx
// Reactの部分を{ useState }に編集
import { useState } from 'react';
import './App.css';

export const App = () => {
  const [incompleteTodos, setIncompleteTodos] = useState(["宿題", "洗濯"]);

 return (
  :
  :
  );
};

const [incompleteTodos, setIncompleteTodos]
未完了のtodoの状態を管理するための配列です。incompleteTodosが初期値。setIncompleteTodosが更新するための変数です。

useState(["宿題", "洗濯"])は初期値のincompleteTodosに宿題と洗濯という2つの値を渡しています。
次にreturn内を編集していきます。まずは未完了のtodoからです。

App.jsx

return (
  :
  :
   <div className="imcompleteArea">
     <p className="title">未完了のtodo</p>
       <ul>
         <li className="todoList">
           <p>テスト</p>
           <button>完了</button>
           <button>削除</button>
         </li>
       </ul>
    </div>
  :
  :
);

↓ // 変更
<div className="imcompleteArea">
  <p className="title">未完了のtodo</p>
    <ul>
      {incompleteTodos.map((todo) => {
         return (
           <li key={todo} className="todoList">
             <p>{todo}</p>
             <button>完了</button>
             <button>削除</button>
           </li>
         );
        })}
    </ul>
</div>

順番に解説していきます。
まずmap関数を用いて先程定義したincompleteTodosの中身を繰り返し処理で表示させます。その際の引数はtodoとします。

key={todo}と記述したのは繰り返し処理によるレンダリングでどのtodoかを判別するためです。最後にpタグの中身は{ }でtodoを囲んで表示させます。

ポイント

  • return内でJavascriptを扱う際は{ }で囲む

同じようにして完了のtodoもuseStateを用いて状態を管理します。
completeAreaを以下のように編集してください。

App.jsx
export const App = () => {
  const [incompleteTodos, setIncompleteTodos] = useState(["宿題", "洗濯"]);
  // 追記
  const [completeTodos, setCompleteTodos] = useState(["掃除"]);
 
 return (
    :
    :
    <div className="completeArea">
      <p className="title2">完了のtodo</p>
      <ul>
        {completeTodos.map((todo) => {
          return (
            <li key={todo} className="todoList">
              <p>{todo}</p>
              <button>戻す</button>
            </li>
          );
        })}
      </ul>
   </div>
  );
};

これで準備が整いました!
それでは順番に機能を実装していきたいと思います。

4. Todoの入力機能

まずは、入力されたtodoの状態を管理するためのuseStateを用意します。
初期値は''で空にしておきましょう。

App.jsx
export const App = () => {
 // 追記
  const [todoText, setTodoText] = useState('');
};

入力されたtodoTextはinputタグのvalueとして設定します。
{ }で変数を囲むのを忘れないでください。

<div className="inputArea">
  <input placeholder="todoを入力" value={todoText} />
  <button>追加</button>
</div>

この状態だと入力欄にテキストを打ち込んでも反応しません。それは初期値の''が常に渡されているためです。
そこでonChangeメソッドを用いてtodoTextの値を更新する関数を作成する必要があります。
inputタグを次のように編集しましょう。

<div className="inputArea">
  <input placeholder="todoを入力" value={todoText} onChange={onChangeTodoText}/>
  <button>追加</button>
</div>

inputにtodoが入力されたら、onChangeメソッドでonChangeTodoTextという関数を呼び出しています。この関数の中でtodoの値を更新する処理を記述していきます。
では、onChangeTodoText関数を定義します。関数はreturnの前に定義しましょう。

export const App = () => {
  // 関数の定義
 const onChangeTodoText = (event) => setTodoText(event.target.value);
};

onChangeTodoTextは引数を取ります。今回はeventを取ります。
event.target.valueは入力されたtodoの値のことです。これをsetTodoText関数の引数として渡すことで入力された値がsetTodoTextとして更新されます。

5. Todoの追加機能

4までの内容でtodoを入力することはできました。
次に追加ボタンを押してtodoを未完了エリアに移動させる機能を実装します。
buttonタグにonClickメソッドでボタンを押したときに関数を呼び出します。
今回はonClickAddという関数名にします。

<div className="inputArea">
  <input placeholder="todoを入力" value={todoText} onChange={onChangeTodoText} />
  <button onClick={onClickAdd}>追加</button>
</div>

ではonClickAddの中身を記述しましょう。
これもreturnの前に記述します。

const onClickAdd = () => {
    if (todoText === '') return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText('');
}

2行目のifは空の入力は無効にするという意味です。

3行目の[...incompleteTodos, todoText]は配列のコピーをしています。
[...変数名]と記述するとその変数の値をコピーできます。すでにある未完了のtodoに新しく入力したtodoTextを加えて、新しくnewTodosという変数に格納しています。

4行目は未完了のtodoのstateを更新したいのでnewTodosをsetIncompleteTodos関数の引数に渡して値を更新します。

5行目は追加ボタンを押した際に入力欄をリセットしたいのでsetTodoText関数を空にします。

6. Todoの削除機能

未完了エリアのtodoを削除する機能を実装します。方針は次の通りです。

1. 削除ボタンにonClickメソッドを設定して関数を呼び出す
2. 削除ボタンを押したら未完了エリアから選んだtodoが削除される

削除ボタンにonClickメソッドを設定

todoを削除する関数をonClickDeleteとします。

 <div className="imcompleteArea">
   <p className="title">未完了のtodo</p>
     <ul>
       {incompleteTodos.map((todo, index) => {
         return (
           <li key={todo} className="todoList">
             <p>{todo}</p>
             <button }>完了</button>
             <button onClick={() => onClickDelete(index)>削除</button>
           </li>
         );
       })}
    </ul>
 </div>

ポイントとしてはmap関数に新しくindexを引数に設定しています。
これはtodoを削除する際に何番目のtodoかを判別する際に必要です。関数の中でもindexは必要なのでonClickDeleteの引数にもindexを渡します。

関数の定義

例によってreturnの前に記述しましょう。

const onClickDelete = (index) => {
    const newTodos = [...incompleteTodos];
    // newTodosから指定のindex番号を1つ削除する
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
};

2行目で現在の未完了のtodoをnewTodosという変数に格納します。

3行目でspliceメソッドを使用しています。spliceは配列の要素を操作できるメソッドで要素の追加や削除することができます。第一引数に削除対象のindexを渡して第二引数は指定したindexから何番目までの要素を操作するかを設定します。(今回は1なのでつまり、指定したtodoのみを削除します。)

4行目で削除し終えた未完了のtodoの状態を更新するためにsetIncompleteTodos関数にnewTodosを渡して値を更新します。

7. Todoの完了機能

未完了のtodoを完了エリアに移動させます。方針は以下の通りです。

1. 完了ボタンにonClickメソッドを設定して関数を呼び出す
2. 完了ボタンを押したら未完了エリアからtodoが削除される
3. 完了ボタンを押したら削除されたtodoが完了エリアに追加される

完了ボタンにonClickメソッドを設定

関数名はonClickCompleteとします。

<div className="imcompleteArea">
  <p className="title">未完了のtodo</p>
    <ul>
      {incompleteTodos.map((todo, index) => {
        return (
          <li key={todo} className="todoList">
            <p>{todo}</p>
            <button onClick={() => onClickComplete(index)}>完了</button>
            <button onClick={() => onClickDelete(index)}>削除</button>
          </li>
         );
       })}
    </ul>
</div>

onClickCompleteにも、どのtodoを完了にするか判別するために引数としてindexを渡しましょう。

関数を定義

  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    // newTodosから指定のindex番号を1つ削除する
    newIncompleteTodos.splice(index, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  };

2~4行目で未完了エリアから選んだtodoを削除しています。[...incompleteTodos]で現在の未完了のtodoをコピーして、変数newIncompleteTodosに格納します。次にspliceメソッドでtodoを削除します。

6~8行目で選んだ未完了エリアと完了エリアにtodoを更新してます。
[...completeTodos, incompleteTodos[index]]で完了エリアに現在の完了のtodoであるcompleteTodosをコピーし、選んだ未完了のtodoであるincompleteTodos[index]を追加し
変数newCompleteTodosに格納します。

最後に未完了エリアのtodoの値と完了エリアのtodoの値をuseStateで更新しています。

8. Todoの戻す機能

機能としてはこれで最後です。方針は以下の通りです。

1. 戻すボタンにonClickメソッドを設定し関数を呼び出す
2. 戻すボタンを押したら完了エリアから選んだtodoを削除する
3. 戻すボタンを押したら未完了エリアに選んだtodoを追加する

完了機能の逆ですね

戻すボタンにonClickメソッドを設定

関数名はonClickBackとします。

 <div className="completeArea">
   <p className="title2">完了のtodo</p>
     <ul>
       {completeTodos.map((todo, index) => {
         return (
           <li key={todo} className="todoList">
             <p>{todo}</p>
             <button onClick={() => onClickBack(index)}>戻す</button>
           </li>
         );
        })}
     </ul>
 </div>

例によってmap関数とonClickBack関数にindexを引数として渡します。

関数を定義

const onClickBack = (index) => {
  const newCompleteTodos = [...completeTodos];
  newCompleteTodos.splice(index, 1);

  const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
  setCompleteTodos(newCompleteTodos);
  setIncompleteTodos(newIncompleteTodos);
};

2~3行目で完了エリアから選んだtodoを削除します。

5~7行目は完了機能の逆なので説明は割愛します。

これで一応機能は実装しました。最終的なコードはこんな感じです。

App.jsx
import { useState } from 'react';
import './App.css';

export const App = () => {
  const [todoText, setTodoText] = useState('');

  const [incompleteTodos, setIncompleteTodos] = useState([]);

  const [completeTodos, setCompleteTodos] = useState([]);

  const onChangeTodoText = (event) => setTodoText(event.target.value);

  const onClickAdd = () => {
    if (todoText === '') return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText('');
  }

  // タスクを削除する
  const onClickDelete = (index) => {
    const newTodos = [...incompleteTodos];
    // newTodosから指定のindex番号を1つ削除する
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };

  // 未完了のタスクを完了タスクへ
  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    // newTodosから指定のindex番号を1つ削除する
    newIncompleteTodos.splice(index, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  };

  // 完了のタスクを未完了のタスクへ
  const onClickBack = (index) => {
    const newCompleteTodos = [...completeTodos];
    newCompleteTodos.splice(index, 1);

    const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
    setCompleteTodos(newCompleteTodos);
    setIncompleteTodos(newIncompleteTodos);
  };

  return (
    <>
      <div className="outline">
        <div className="inputArea">
          <input placeholder="todoを入力" value={todoText} onChange={onChangeTodoText} />
          <button onClick={onClickAdd}>追加</button>
        </div>
        <div className="imcompleteArea">
          <p className="title">未完了のtodo</p>
          <ul>
            {incompleteTodos.map((todo, index) => {
              return (
                <li key={todo} className="todoList">
                  <p>{todo}</p>
                  <button onClick={() => onClickComplete(index)}>完了</button>
                  <button onClick={() => onClickDelete(index)}>削除</button>
                </li>
              );
            })}
          </ul>
        </div>
        <div className="completeArea">
          <p className="title2">完了のtodo</p>
          <ul>
            {completeTodos.map((todo, index) => {
              return (
                <li key={todo} className="todoList">
                  <p>{todo}</p>
                  <button onClick={() => onClickBack(index)}>戻す</button>
                </li>
              );
            })}
          </ul>
        </div>
      </div>
    </>
  );
};

それでは、ここからコンポーネント化をしてコードをスッキリさせたいと思います。

9. コンポーネント化

今回はインプットエリア、未完了エリア、完了エリアの3つにコンポーネント化していきます。
まずはインプットエリアから始めます。src配下にcomponentsディレクトリを作成し、InputTodo.jsxというファイルを作成します。

src
|-components
      |-InputTodo.jsx
|-index.js
|-App.jsx
|-App.css

inputAreaのコンポーネント化

それではInputTodo.jsxに必要な記述をしていきます。
App.jsxのreturn内のinputAreaの中身をInputTodo.jsxのreturn内に切り抜きします。
また、App.jsxでこのファイルを使用できるようにexportします。

InputTodo.jsx
import React from 'react';

export const InputTodo = () => {
  return (
    <div className="inputArea">
      <input placeholder="todoを入力" value={todoText} onChange={onChangeTodoText} />
      <button onClick={onClickAdd}>追加</button>
    </div>
  );
};

次にApp.jsxを編集します。
InputTodo.jsxをimportします。

App.jsx
import { useState } from 'react';
import './App.css';
// 追記
import { InputTodo } from './components/InputTodo';
:
:
return (
  <div className="outline">
    <InputTodo />
    <div className="imcompleteArea">
    :
    :
);

このままではInputTodo.jsx内にtodoTextonChangeTodoTextなどの変数や関数が設定されてないのでエラーが起きてしまいます。なのでpropsを用いてInputTodo.jsxに必要な要素を渡します。

App.jsx
:
:
return (
  :
  :
  <InputTodo 
    todoText={todoText} 
    onChange={onChangeTodoText} 
    onClick={onClickAdd} 
  />
);

InputTodoに3つのpropsを設定します。
それぞれtodoTextonChangeonClickとします。

InputTodo.jsx
import React from 'react';

export const InputTodo = (props) => {
  const { todoText, onChange, onClick } = props;
  return (
    <div className="inputArea">
    <input 
      placeholder="todoを入力" 
      value={todoText} 
      onChange={onChange} 
    />
    <button onClick={onClick}>追加</button>
  </div>
  );
};

propsを引数に取り、それぞれの値として渡します。const { todoText, onChange, onClick } = props;は分割代入といってメソッドを書くときに簡略化することができます。例えば本来ならprops.todoTextprops.onChangeと書かないといけないところをtodoTextonChangeと省略できます。
これでインプットエリアのコンポーネント化は完了です。

imcompleteAreaのコンポーネント化

コンポーネント用のファイルを作成します。components配下にIncompleteTodos.jsxを作成します。

src
|-components
      |-InputTodo.jsx
      |-IncompleteTodos.jsx
|-index.js
|-App.jsx
|-App.css

同じようにincompleteAreaの中身を切り取ります。

IncompleteTodos.jsx
import React from 'react';

export const IncompleteTodos = () => {
  return (
    <div className="imcompleteArea">
      <p className="title">未完了のtodo</p>
      <ul>
        {incompleteTodos.map((todo, index) => {
          return (
            <li key={todo} className="todoList">
              <p>{todo}</p>
              <button onClick={() => onClickComplete(index)}>完了</button>
              <button onClick={() => onClickDelete(index)}>削除</button>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

次にApp.jsxを編集します。

App.jsx
import { useState } from 'react';
import './App.css';
import { InputTodo } from './components/InputTodo';
// 追記
import { IncompleteTodos } from './components/IncompleteTodos';
:
:
rerun (
  :
  :
   <IncompleteTodos />
  :
);

それでは必要なpropsを設定します。

App.jsx
:
:
return (
  :
  <IncompleteTodos 
    todos={incompleteTodos} 
    onClickComplete={onClickComplete} 
    onClickDelete={onClickDelete} 
  />
  :
);

IncompleteTodos.jsx内で使用する変数と関数をそれぞれ渡します。

IncompleteTodos.jsx
import React from 'react';

export const IncompleteTodos = (props) => {
  const { todos, onClickComplete, onClickDelete } = props;
  return (
    <div className="imcompleteArea">
      <p className="title">未完了のtodo</p>
      <ul>
     // incompleteTodosをtodosに変更
        {todos.map((todo, index) => {
          return (
            <li key={todo} className="todoList">
              <p>{todo}</p>
              <button onClick={() => onClickComplete(index)}>完了</button>
              <button onClick={() => onClickDelete(index)}>削除</button>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

先程と同じように分割代入でpropsを渡します。
それでは最後に完了エリアのコンポーネント化をしていきます。

completeAreaのコンポーネント化

コンポーネント用のファイルを作成します。components配下にCompleteTodos.jsxを作成します。

src
|-components
      |-InputTodo.jsx
      |-IncompleteTodos.jsx
      |-CompleteTodos.jsx
|-index.js
|-App.jsx
|-App.css

ここも同様にcompleteAreaの中身を切り取ります。

CompleteTodos.jsx
import React from 'react';

export const CompleteTodos = () => {
  return (
    <div className="completeArea">
      <p className="title2">完了のtodo</p>
      <ul>
        {completeTodos.map((todo, index) => {
          return (
            <li key={todo} className="todoList">
              <p>{todo}</p>
              <button onClick={() => onClickBack(index)}>戻す</button>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

次にApp.jsxを編集します。

App.jsx
import { useState } from 'react';
import './App.css';
import { InputTodo } from './components/InputTodo';
import { IncompleteTodos } from './components/IncompleteTodos';
// 追記
import { CompleteTodos } from './components/CompleteTodos';

:
return (
  :
  :
  <CompleteTodos />
  :
);

それでは必要なpropsを設定します。

App.jsx
:
:
return (
  :
   <CompleteTodos 
     todos={completeTodos} 
     onClickBack={onClickBack} 
   />
);

それではpropsを渡しましょう。

CompleteTodos.jsx
import React from 'react';

export const CompleteTodos = (props) => {
  const { todos, onClickBack} = props;
  return (
    <div className="completeArea">
      <p className="title2">完了のtodo</p>
      <ul>
        {todos.map((todo, index) => {
          return (
            <li key={todo} className="todoList">
              <p>{todo}</p>
              <button onClick={() => onClickBack(index)}>戻す</button>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

以上でコンポーネント化完了です!

終わりに

記事が長くなってしまいましたが、最後まで読んでいただきありがとうございました!
React学習の助けになれば幸いです。。。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?