🚀
Next.jsとTypeScriptを使ったTODOアプリのフロントエンド開発
サクッと作れる Laravel 11 で超簡単な RESTful API を実装!! のフロントエンドです。
types
types\TodoTypes.ts
export type TodoTypes = {
id: string;
title: string;
};
export type TodoActionParams = {
todos: TodoTypes[];
setTodos: (todos: TodoTypes[]) => void;
};
export type AddTodoParams = TodoActionParams & {
newTodo: string;
};
export type DeleteTodoParams = TodoActionParams & {
id: string;
};
services
services\todoService.ts
import { TodoTypes, AddTodoParams, DeleteTodoParams } from "../types/TodoTypes";
const API_URL = "http://localhost:8080/api/todos";
export const fetchTodos = (): Promise<TodoTypes[]> => {
return fetch(API_URL)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => data.todos || [])
.catch((error) => {
console.error("Error fetching todos:", error);
return [];
});
};
export const addTodo = ({ newTodo, todos, setTodos }: AddTodoParams): void => {
fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title: newTodo }),
})
.then((response) => response.json())
.then((data) => setTodos([...todos, data.todo]))
.catch((error) => console.error("Error adding todo:", error));
};
export const updateTodo = (id: string, title: string): Promise<void> => {
return fetch(`${API_URL}/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title }),
})
.then((response) => response.json())
.catch((error) => {
console.error("Error updating todo:", error);
throw error;
});
};
export const deleteTodo = ({ id, todos, setTodos }: DeleteTodoParams): void => {
fetch(`${API_URL}/${id}`, {
method: "DELETE",
})
.then(() => {
const filteredTodos = todos.filter((todo) => todo.id !== id);
setTodos(filteredTodos);
})
.catch((error) => console.error("Error deleting todo:", error));
};
page
page.tsx
"use client";
import { useEffect, useState } from "react";
import { fetchTodos, addTodo, updateTodo, deleteTodo } from "./services/TodoService";
import { TodoTypes } from "./types/TodoTypes";
import Button from "./components/Button";
import InputField from "./components/InputField";
export default function Home(): JSX.Element {
const [todos, setTodos] = useState<TodoTypes[]>([]);
const [newTodo, setNewTodo] = useState("");
useEffect(() => {
fetchTodos().then((todos) => {
if (Array.isArray(todos)) {
setTodos(todos);
} else {
console.error("Fetched data is not an array:", todos);
setTodos([]);
}
});
}, []);
return (
<main className="flex min-h-screen flex-col items-center justify-center p-10 bg-gray-100">
<div className="mb-4 w-full max-w-3xl flex">
<InputField
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add new todo"
/>
<Button
onClick={() => addTodo(newTodo, todos, setTodos)}
color="blue"
text="Add Todo"
/>
</div>
<div className="w-full max-w-3xl">
{todos.map((todo: TodoTypes) => (
<div
key={todo.id}
className="flex items-center justify-between p-3 mb-2 bg-white border rounded shadow"
>
<InputField
value={todo.title}
onChange={(e) => {
const updatedTitle = e.target.value;
const updatedTodos = todos.map((t) =>
t.id === todo.id ? { ...t, title: updatedTitle } : t
);
setTodos(updatedTodos);
}}
/>
<Button
onClick={() => updateTodo(todo.id, todo.title)}
color="green"
text="Update"
/>
<Button
onClick={() => deleteTodo(todo.id, todos, setTodos)}
color="red"
text="Delete"
/>
</div>
))}
</div>
</main>
);
}
components
components\Button.tsx
import React from "react";
type TodoButtonProps = {
onClick: () => void;
color: string;
text: string;
};
const Button: React.FC<TodoButtonProps> = ({ onClick, color, text }) => {
const baseStyle = "p-2 ml-2 text-white rounded shadow focus:outline-none";
const colorStyle = `bg-${color}-500 hover:bg-${color}-600`;
return (
<button onClick={onClick} className={`${baseStyle} ${colorStyle}`}>
{text}
</button>
);
};
export default Button;
components\InputField.tsx
import React from 'react';
type InputFieldProps = {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
className?: string;
};
const InputField: React.FC<InputFieldProps> = ({ value, onChange, placeholder, className }) => {
return (
<input
type="text"
value={value}
onChange={onChange}
placeholder={placeholder}
className={className || "flex-1 p-2 mr-2 border rounded shadow text-black focus:outline-none"}
/>
);
};
export default InputField;
Discussion