LoginSignup
0
1

React+TypeScript+axiosでTODOアプリ作成(Docker環境)

Last updated at Posted at 2023-05-26

はじめに

ReactとTypeScriptでTODOアプリを作成しました。
その作成工程を記します。
(工程の最後にtailwindcssをあてましたが、その部分は別記事となります。)

環境構築と前準備

以下の記事を参考に環境構築を行って下さい。
Docker + React + TypeScript の 環境構築方法

バックエンド(API)は以下の記事を参考に作成し、起動しておいてください。
DockerとLaravel10でAPI作成(Postmanで動作確認)

API通信用にaxiosを使えるようにします。

axiosとは非同期通信でデータを取得するためのモジュールです。
Node.jsで書かれています。 読み方は、アクシオスと読みます。

axiosインストールコマンド

npm install axios

srcフォルダ下に機能毎の新たなフォルダを作成

types (型定義用)
apis (APIとの通信用)
hooks (hook用)
components (システム構成要素用)

機能毎にソースファイルを作成

typesフォルダ

typesフォルダ内にTodo.tsファイルを作成し、以下を入力

Todo.tsソースコード
Todo.ts
export type Todo = {
  id: string;
  text: string;
  updated_at: string;
};

apisフォルダ

apisフォルダ内にtodos.tsファイルを作成し、以下を入力

todos.tsソースコード
todos.ts
import axios from "axios";

const todoDataUrl = "http://localhost/api/todo"

export const getAllTodosData = async () => {
    const response = await axios.get(todoDataUrl);
    return response.data;
};
export const addTodoData = async (text: string) => {
  const response = await axios.post(`${todoDataUrl}`,{text:text});
  return response.data;
};
export const deleteTodoData = async (id: string) => {
  await axios.delete(`${todoDataUrl}/?id=${id}`);
  return id;
};
export const updateTodoData = async (id: string, text: string | undefined) => {
  const response = await axios.put(`${todoDataUrl}/?id=${id}`, {text:text});
  return response.data;
};

hooksフォルダ

hooksフォルダ内にuseTodo.tsファイルを作成し、以下を入力

useTodo.tsソースコード
useTodo.ts
import { useState, useEffect } from "react";
import * as todoData from "../apis/todos";
import { Todo } from "../types/Todo";

export const useTodo = () => {
  const [todoList, setTodoList] = useState<Todo[]>([]);

  useEffect(() => {
    todoData.getAllTodosData().then((todo) => {
      setTodoList([...todo].reverse());
    });
  }, []);

  const updateTodoListItem = (id: string, value: string) => {
    const todoItem = todoList.find((item: Todo) => item.id === id);
    if(!todoItem){
        return;
    }
    const newTodoItem: string = value;
    
    todoData.updateTodoData(id, newTodoItem).then((updatedTodo) => {
      const newTodoList = todoList.map((item) => (item.id !== updatedTodo.id ? item : updatedTodo));
      setTodoList(newTodoList);
    });
  };

  const addTodoListItem = (todoText: string) => {
    const newTodoItem = todoText;

    todoData.addTodoData(newTodoItem).then((addTodo) => {
      setTodoList([addTodo, ...todoList]);
    });
  };

  const deleteTodoListItem = (id: string) => {
    todoData.deleteTodoData(id).then((deletedid) => {
      const newTodoList = todoList.filter((item) => item.id !== deletedid);
      setTodoList(newTodoList);
    });
  };

  return { todoList, updateTodoListItem, addTodoListItem, deleteTodoListItem };
};

componentsフォルダ

componentsフォルダ内に各ファイルを作成し、
TodoTitle.tsx
TodoAdd.tsx
Todoitem.tsx
TodoList.tsx
App.tsx
以下を入力

TodoTitle.tsxソースコード
TodoTitle.tsx
import { memo } from "react";

export const TodoTitle = memo(({ title, as }: { title: string; as: string}) => {
  if (as === "h1") {
    return <h1>{title}</h1>;
  } else if (as === "h2") {
    return <h2>{title}</h2>;
  } else {
    return <p>{title}</p>;
  }
});
TodoAdd.tsxソースコード
TodoAdd.tsx
import { RefObject } from "react";

export const TodoAdd = ({ buttonText, inputEl, handleAddTodoListItem }: { buttonText: string; inputEl: RefObject<HTMLTextAreaElement>; handleAddTodoListItem: () => void }) => {
  return (
    <>
      <textarea ref={inputEl} />
      <button onClick={handleAddTodoListItem} >{buttonText}</button>
    </>
  );
};
Todoitem.tsxソースコード
Todoitem.tsx
import React,{useState} from "react";
import { Todo } from "../types/Todo";

export const TodoItem = ({ todo, updateTodoListItem, deleteTodoListItem }: { todo: Todo; updateTodoListItem: any; deleteTodoListItem: any }) => {
  
  const [value, setValue] = useState("");
  const [disabled, setDisabled] = useState(true);
  const [active, setActive] = useState(false);

  const handleChangeText = (event: { target: { value: React.SetStateAction<string>; }; }) => {
    if(event.target.value===""){
      setDisabled(true);
      setActive(false);  
    }else{
      setDisabled(false);
      setActive(true);
      setValue(event.target.value);
    }
  };
  
  const handleUpdateTodoListItem = () =>{
    updateTodoListItem(todo.id,value);
    setDisabled(true);
    setActive(false);
  }

  const handleDeleteTodoListItem = () => deleteTodoListItem(todo.id);

  const year = todo.updated_at.substring(0,4);
  const month = todo.updated_at.substring(5,7);
  const day = todo.updated_at.substring(8,10);
  const hour = String(Number(todo.updated_at.substring(11,13)) + 9);
  const minute = todo.updated_at.substring(14,16);
  const second = todo.updated_at.substring(17,19);
  const datetime = year+""+month+""+day+""+hour+""+minute+""+second+""

  return (
    <>
      <td>{todo.id}</td>
      <td><input type="text" defaultValue={todo.text} onChange={handleChangeText} /></td>
      <td><input type="text" value={datetime} /></td>
      <td><button id={todo.id} onClick={handleUpdateTodoListItem} disabled={disabled}>{!active ? "":"更新"}</button></td>
      <td><button type="button" onClick={handleDeleteTodoListItem}>削除</button></td>
    </>
  );
};
TodoList.tsxソースコード
TodoList.tsx
import { TodoTitle } from "./TodoTitle";
import { TodoItem } from "./TodoItem";
import { Todo } from "../types/Todo";

export const TodoList = ({
  todoList,
  updateTodoListItem,
  deleteTodoListItem,
  title,
  as,
}: {
  todoList: Todo[];
  updateTodoListItem: (id: string,value: string) => void;
  deleteTodoListItem: (id: string) => void;
  title: string;
  as: string;
}) => {
  return (
    <>
      {todoList.length !== 0 && (
        <>
        <TodoTitle title={title} as={as} />
        <div>
          <table>
            <tr>
              <th>NO</th><th>     TODO 内容     </th><th>TODO追加更新日時</th><th> 更新 </th><th> 削除 </th>
            </tr>
            {todoList.map((todo) => (
              <tr key={todo.id}>
                <TodoItem todo={todo} key={todo.id} updateTodoListItem={updateTodoListItem} deleteTodoListItem={deleteTodoListItem} />
              </tr>
            ))}
          </table>
        </div>
        </>
      )}
    </>
  );
};
App.tsxソースコード
App.tsx
import React, { useRef } from "react";

import { useTodo } from "../hooks/useTodo";
import { Todo } from "../types/Todo";
import { TodoTitle } from "./TodoTitle";
import { TodoAdd } from "./TodoAdd";
import { TodoList } from "./TodoList";

function App() {
  const { todoList, updateTodoListItem, addTodoListItem, deleteTodoListItem } = useTodo();

  const inputEl = useRef<HTMLTextAreaElement>(null);

  const handleAddTodoListItem = () => {
    if (inputEl.current?.value === "") {
      return;
    }
    addTodoListItem(inputEl.current!.value);
    inputEl.current!.value = "";
  };

  const incompletedList = todoList.filter((todo: Todo) => todo.text);

  return (
    <>
  <TodoTitle title="TODOアプリ" as="h1" />
  <TodoAdd buttonText="追加" inputEl={inputEl} handleAddTodoListItem={handleAddTodoListItem} />
  <TodoList todoList={incompletedList} updateTodoListItem={updateTodoListItem} deleteTodoListItem={deleteTodoListItem} title="TODOリスト" as="h2" />
  </>
  );
}

export default App;

srcフォルダ

srcフォルダ内のApp.tsxファイルは削除しておいてください。

srcフォルダ内にあるindex.tsxファイルの内容を以下に変更

index.tsxソースコード
index.tsx
import React from "react";
import ReactDOM from "react-dom";
import './index.css';
import App from "./components/App";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

プログラムの実行

API(バックエンド側)が起動している状態で、下記コマンドを実行します。

docker-compose build

実行後

docker-compose up –d

を実行

コンテナが起動している状態になりますので、Webブラウザを開いて「 http://localhost:8000/ 」にアクセスします。

以上になります。

最後に

今回、実行後の画面画像等については、記事にしておりません。
今後、TailWindCssおよびCSSで加工した画面と作成工程を、別記事にて公開する予定です。
画面デザインに関しましては、そちらの記事を参考にして頂くか、
もしくは、ご自身でCSSをあてて、画面の見栄えを良くして頂けましたら幸いです。

最後まで読んでいただきまして、ありがとうございました。

0
1
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
0
1