📘

ReactでシンプルなTodoアプリを作成する

2024/03/18に公開

目的

  1. Reactについて理解を深めたい
  2. TypeScriptについて理解を深めたい
  3. 定期的に自分のコードを見直す為に何か更新できるアプリを作りたい
  4. AIにコード生成を頼らない(リファクタリングに使うのは可)

以上の理由から比較的シンプルで尚且つ拡張しようと思えばかなり幅広くアップデートができると感じ作成しました。
その為こちらの記事も大きな更新が発生した際は更新する予定です。

使用技術

フロントエンド

バックエンド

コンポーネント設計

Todo list V.1として以下のような必要最低限の要素での実装です。

  • 自身の名前を記載したHeader
  • Todoの内容と追加するためのSubmit button
  • 追加された要素を表示するTodo list
├── components
    ├── Header
    │   └── Header.tsx
    ├── TodoForm
    │   └── TodoForm.tsx
    ├── TodoList
    │   └── TodoList.tsx
    └── index.ts

以上のような設計にしました。
初学者の為あまり他の人の設計を真似するのはどうなのかと思い一旦思いつきのまま実装します。

実装の流れ(下準備)

Todo Form

まずはTodoFormにTodoの要素であるinput要素と要素をTodoListに追加するためのbutton要素を追加します。

import React from "react";

// style
import { Input } from "@chakra-ui/input";
import { Button } from "@chakra-ui/button";

export const TodoForm: React.FC = () => {
  return (
    <>
      <Input placeholder="Todo title" />
      <Button color="#81A1C1">Add todo</Button>
    </>
  );
};

Todo List

現状は要素を追加する機能が無いのでサンプルのデータを直接書いています。

import React from "react";

// style
import { UnorderedList, ListItem, Box } from "@chakra-ui/layout";

export const TodoList: React.FC = () => {
  return (
    <Box>
      <UnorderedList margin={"auto"} width={"fit-content"}>
        <ListItem>Learning english</ListItem>
        <ListItem>Lunch with friend</ListItem>
        <ListItem>Booking train</ListItem>
        <ListItem>Shopping</ListItem>
      </UnorderedList>
    </Box>
  );
};

デザインは苦手なので不恰好ですがその内綺麗に整えたいです(誰か...)

機能の実装編

それでは下準備があらかた終了したので実際にinput要素に入力した内容をbuttonを押すことでListに追加される機能を実装していきます。

inputの処理

input要素に入力された内容をListに追加する為に保持する必要があります。
その時に使うのがuseStateというものです。

// hooks
const [todoTitle, setTodoTitle] = useState();

// handler
const handleTodoTitleChange = (event: any) => {
  setTodoTitle(event.target.value);
};

<Input placeholder={"Todo title"} onChange={handleTodoTitleChange} />

状態を管理するuseStateと入力された値を親コンポーネントに渡すためにPropsを使います。

詳しくはこちらを

// interface
interface TodoTitleProps {
  onTodoTitleChange: (todoTitle: string) => void;
}

export const TodoForm: React.FC<TodoTitleProps> = ({ onTodoTitleChange }) => {
  // hooks
  const [todoTitle, setTodoTitle] = useState("");

  // handler
  const handleTodoTitleChange = (event: any) => {
    setTodoTitle(event.target.value);
  };

  // onClick
  const onAddTodo = () => {
    onTodoTitleChange(todoTitle);
    setTodoTitle("");
    console.log("button click");
  };

  return (
    <HStack alignItems={"center"} width={"auto"} justifyContent={"center"}>
      <Input
        placeholder={"Todo title"}
        onChange={handleTodoTitleChange}
        value={todoTitle}
      />
      <Button color="#81A1C1" onClick={onAddTodo}>
        Add todo
      </Button>
    </HStack>
  );
};

Propsの受け取りと受け渡し

今回であればTodoFormコンポーネントで入力されたテキストを親コンポーネンであるAppに渡し、親コンポーネンで別の子コンポーネントであるTodoListコンポーネントに渡します。

// import component
import { Header, TodoForm, TodoList } from "./components/";

import { useState } from "react";

function App() {
  // hooks
  const [todoTitle, setTodoTitle] = useState("");

  // handler
  const handleTodoTitleChange = (todoTitle: string) => {
    setTodoTitle(todoTitle);
  };
  return (
    <>
      <Header />
      <TodoForm onTodoTitleChange={handleTodoTitleChange} />
      <TodoList todoTitle={todoTitle} />
    </>
  );
}

export default App;

TodoListコンポーネントでは送られたPropsを受け取り表示します。

import React from "react";

// style
import { UnorderedList, ListItem, Box } from "@chakra-ui/layout";

// interface
interface todoTitleProps {
  todoTitle: string;
}

export const TodoList: React.FC<todoTitleProps> = ({ todoTitle }) => {
  return (
    <Box>
      <UnorderedList margin={"auto"} width={"fit-content"}>
        <ListItem>{todoTitle}</ListItem>
      </UnorderedList>
    </Box>
  );
};

これで表示は出来ましたが、一つ入力しまた新たに入力した場合前回の内容が書き変わってしまうので受け取った内容を配列に格納しましょう。

export const TodoList: React.FC<todoTitleProps> = ({ todoTitle }) => {
  // hooks
  const [todoList, setTodoList] = useState<string[]>([]);

  // function
  const addTodo = () => {
    setTodoList((prevTodoList) => [...prevTodoList, todoTitle]);
  };

  // useEffect
  React.useEffect(() => {
    if (todoTitle !== "") {
      addTodo();
    }
  }, [todoTitle]);

  return (
    <Box>
      <UnorderedList margin={"auto"} width={"fit-content"}>
        {todoList.map((item, index) => (
          <ListItem key={index}>{item}</ListItem>
        ))}
      </UnorderedList>
    </Box>
  );
};

Propsとして渡されたTodoTitleuseEffectを使って受け渡される度にtodoList配列に格納するようにしました。
useEffect内のifは初回レンダリングが発生し空の配列が作成されてしまうのでそれを回避する為です

一旦終わり

必要最低限の

  • Todoの内容を入力
  • 入力された内容がリストで表示される

以上の内容が出来ました。これからもっと拡張していきます。

figmaに書いたのにチェックボックスの実装が出来ていない。。次回の更新で追加予定

お願い

ソースコードはこちらに公開しています。

  • 導入すべき機能
  • デザインのアドバイス
  • ソースコードのリファクタリング
  • 変数や関数など命名の指摘

などなど自分でも改善していきますがまだまだReactを理解出来ていない部分が多いのでご指摘いただければ幸いです。Issuesにて管理していきますのでどんどん書き込んで下さい。

Discussion