React × Bootstrap5でTodoアプリを作成する方法!(サンプル・解説付き)

React

今回はReactでTodoアプリを作成してみました。
create-react-appを使用して、プロジェクトを作成した後に、Todoアプリを作成していきます。
create-react-appReactのプロジェクトを簡単に作成してくれるツールです。

Reactのバージョン17.0.2で下記のようなTodoアプリを作成してみました。
プロジェクトの立ち上げ方から、コードの解説までを記載しています。

create-react-appでプロジェクトを作成する

npxでcreate-react-appを指定して、プロジェクトを作成します。
今回はreact_todo_appという名前でプロジェクトを作成しました。

$ npx create-react-app react_todo_app

コマンドを実行すると、下記のようにプロジェクトが作成されます。

$ npx create-react-app react_todo_app

Creating a new React app in /Users/yasuaki/react_todo_app.

Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts with cra-template...
//...省略...
cd react_todo_app
  npm start

Happy hacking!

この状態で、上記に書かれているようにプロジェクトのディレクトリに移動して、npm startを実行してみます。
そうすると、下記のように初期で用意されている画面が表示されます。

create-react-appの初期画面

※ちなみにプロジェクトに大文字を含めると、下記のようにnpmの命名制限でエラーになります。

$ npx create-react-app reactTodoApp
Cannot create a project named “reactTodoApp” because of npm naming restrictions:

* name can no longer contain capital letters

Please choose a different project name.

Todoコンポーネントを作成する

今回はTodo画面のコンポーネントを作って、初期で表示されているAppコンポーネントをTodoコンポーネントに切り替えます。
まずは、下記のようにTodo.cssTodo.jsファイルを追加します。

ReactでTodoコンポーネントの準備

Todo.cssを作成する

Todo画面で使用するスタイルを定義します。
今回はBootstrap5を読み込むだけにしました。

Todo.cssを開いて、下記のようにBootstrap5を読み込むようにします。

@import url('https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css');

Todo.jsを作成する

Reactのコンポーネントの書き方には、クラスコンポーネントと関数コンポーネントが存在します。
今回は関数コンポーネントの書き方で、Todo.jsというファイルにTodoというコンポーネントを作成しました。

import './Todo.css'
import React, { useState } from 'react'

export const Todo = () => {
  const [todoText, setTodoText] = useState('')
  const [todos, setTodoList] = useState([])

  const handleAddTodo = () => {
    if (todoText === '') {
      alert('TODOを入力してください')
      return
    }

    setTodoList([...todos, todoText ])
    setTodoText('')
  }

  const handleChaneText = (e) => {
    setTodoText(e.target.value)
  }

  const ShowTodoList = () => {
    if (todos.length === 0) {
      return ''
    }

    const handleDeleteTodo = (paramIndex) => {
      const newTodos = todos.filter((data, index) => { return index !== paramIndex })
      setTodoList([...newTodos])
    }

    const list = todos.map((data, index) => {
      return (
        <li key={index} className="list-group-item d-flex justify-content-between align-items-center">
          {data}
          <button className='btn btn-danger' onClick={() => handleDeleteTodo(index)}>
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-trash" viewBox="0 0 16 16">
              <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
              <path fillRule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
            </svg>
          </button>
        </li>
      )
    })

    return (
      <ul className="list-group mt-3">
        {list}
      </ul>
    )
  }

  return (
    <div>
      <nav className="navbar navbar-dark bg-dark">
        <div className="container-fluid">
          <span className="navbar-brand">Todo App</span>
        </div>
      </nav>
      <div className="container pt-3 w-75">
        <div className="d-flex">
          <input type="text" className="form-control" value={todoText} onChange={handleChaneText} />
          <button type="button" className="btn btn-primary ms-3" onClick={handleAddTodo}>
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-plus" viewBox="0 0 16 16">
              <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
            </svg>
          </button>
        </div>
        <ShowTodoList />
      </div>
    </div>
  )
}

export default Todo

解説

最初にimport文を書いて、先ほど作成したcssとコンポーネントで使用するuseStateを読み込んでいます。

import './Todo.css'
import React, { useState } from 'react'

その後に関数コンポーネントを定義しています。

export const Todo = () => {
  // コンポーネントの内容
}

export default Todo

最後にexportを書くことで、他のファイルからimportできるようにしています。

useStateで変数初期化

コンポーネントの内容を確認していきます。

最初にuseStateを使って、変数を定義しています。
useStateReactで変数の状態を管理(保持・更新)するために使用する関数です。

const [todoText, setTodoText] = useState('')
const [todos, setTodoList] = useState([])

constで定義した左側は、値を保持するための変数で、右側が値を更新するための関数になります。
上記の例で行くと、todoTextが変数で、setTodoTexttodoTextに値を設定するための関数です。

その下にはTODO一覧の状態を保持するためのtodos配列と設定するためのsetTodoListを定義しています。

handleAddTodoの定義

その次にhandleAddTodoを作成しました。
これは、TODOの追加ボタンが押された時に動かす関数です。

const handleAddTodo = () => {
  if (todoText === '') {
    alert('TODOを入力してください')
    return
  }

  setTodoList([...todos, todoText ])
  setTodoText('')
}

todoTextに入力したTODOを反映させますが、入ってなかったらアラートを表示します。
入っていた場合は先ほど定義したTODO一覧配列に、入力されたtodoTextを下記のように反映させています。

setTodoList([...todos, todoText ])

その後にsetTodoTexttodoTextに入っている内容(テキストボックスの内容)を空にしています。

handleChangeTextの定義

次にhandleChaneTextを作成しました。
これはテキストボックスに入力された値をtodoText変数に反映させるために作成しました。

const handleChaneText = (e) => {
  setTodoText(e.target.value)
}

ReactのonChangeイベントに、これを設定すると引数のe.target.valueで入力した値が取得できます。
取得した値をsetTodoText関数でtodoTextに反映させています。

ShowTodoListコンポーネント

その次にShowTodoListコンポーネントを作成しています。
これは、TODO一覧の行のひとつになります。

ReactでTodoリスト作成(行コンポーネント)

const ShowTodoList = () => {
  if (todos.length === 0) {
    return ''
  }

  const handleDeleteTodo = (paramIndex) => {
    const newTodos = todos.filter((data, index) => { return index !== paramIndex })
    setTodoList([...newTodos])
  }

  const list = todos.map((data, index) => {
    return (
      <li key={index} className="list-group-item d-flex justify-content-between align-items-center">
        {data}
        <button className='btn btn-danger' onClick={() => handleDeleteTodo(index)}>
                     <!---省略--->
        </button>
      </li>
    )
  })

  return (
    <ul className="list-group mt-3">
      {list}
    </ul>
  )
}

まず、一覧配列(todos)にデータがなかったら空文字列を返しています。

その次にhandleDeleteTodo関数を定義しています。
これは削除アイコンを押された時に動く関数です。JavaScriptのfilterを使用して値を取り出して、対象の物以外をTodo一覧配列に設定しています。

次にlist変数に対して、行のタグを作成しています。
一覧配列(todos)をmap関数を使用してループさせて、タグを返します。

dataに保持されている一覧のTodoデータが入っていて、indexは配列のキーになります。
liタグのリストにTODOを表示させていて、削除アイコンを右側に配置しています。

削除アイコンを押したら、先ほど定義したhandleDeleteTodoを呼び出して、一覧配列(todos)から削除するといった形です。

最後にulタグで作成したliタグを囲って、呼び出し元に返します。

Todoコンポーネントのタグ定義

最後にTodoコンポーネントで返すタグを定義しています。
一番上にナビバーを配置し、その下に入力エリアと、TODO一覧(ShowTodoList)を配置するといった形です。

  return (
    <div>
      <nav className="navbar navbar-dark bg-dark">
        <div className="container-fluid">
          <span className="navbar-brand">Todo App</span>
        </div>
      </nav>
      <div className="container pt-3 w-75">
        <div className="d-flex">
          <input type="text" className="form-control" value={todoText} onChange={handleChaneText} />
          <button type="button" className="btn btn-primary ms-3" onClick={handleAddTodo}>
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-plus" viewBox="0 0 16 16">
              <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
            </svg>
          </button>
        </div>
        <ShowTodoList />
      </div>
    </div>
  )

テキストボックスの内容を変えた場合にonChangeが動きます。
自作していたhandleChaneText関数を設定しています。テキストの変更時に動いて、todoText変数に入力した内容が更新されます。

追加ボタンを押すと、onClickに設定しているhandleAddTodo関数が動きます。
この関数で、一覧配列(todos)にtodoText変数の値を追加します。

追加された内容が、ShowTodoListコンポーネントで表示されます。

index.jsで作成したコンポーネントを読み込む

作成したコンポーネントを最初から用意されているAppコンポーネントからTodoに切り替えます。
初期状態ではsrc/index.jsで、Appを読み込んでコンポーネントを表示しています。

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// ---ここから削除---
// import App from './App';
// ---ここまで削除---
import Todo from './Todo'; //追加する
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    {/*---ここから削除---
      <App /> 
    ---ここまで削除---*/}
    <Todo /> {/*---追加する---*/}
  </React.StrictMode>,
  document.getElementById('root')
);

//---省略---

import文でTodoコンポーネントを呼び出した後に、AppコンポーネントからTodoコンポーネントに切り替えました。

動作を確認する

プロジェクトを作成したディレクトリでnpm startコマンドを実行すると、作成したアプリケーションが動きます。
`http://localhost:3000/` にアクセスして開くと、下記のようにTODOを入力するテキストボックスと追加ボタンが表示されます。

ReactでTodoアプリ作成・動作環境

入力して、追加ボタンを押すとリストが増えます。

ReactでTodoアプリでTodo追加

削除ボタンを押すと消すことが可能です。

ReactでTodoアプリでTodo削除

終わりに

今回はReactを使用して、Todoアプリを作成してみました。
ソースコードは、下記githubリポジトリで公開しています。
GitHub - YasuakiHirano/react-todo-app
Contribute to YasuakiHirano/react-todo-app development by creating an account on GitHub.

コメント

タイトルとURLをコピーしました