🚀

Next.jsとTypeScriptを使ったTODOアプリのフロントエンド開発

2024/05/10に公開

サクッと作れる Laravel 11 で超簡単な RESTful API を実装!! のフロントエンドです。
https://zenn.dev/0bookbook/articles/11b168c2c228d7

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;
GitHubで編集を提案

Discussion