はじめに
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ソースコード
export type Todo = {
id: string;
text: string;
updated_at: string;
};
apisフォルダ
apisフォルダ内に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ソースコード
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ソースコード
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ソースコード
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ソースコード
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ソースコード
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ソースコード
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ソースコード
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をあてて、画面の見栄えを良くして頂けましたら幸いです。
最後まで読んでいただきまして、ありがとうございました。