0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.jsで編集フォームを作成しました

Last updated at Posted at 2025-02-10

Next.jsで編集フォームを作成

こんにちは、jjj0331です。今回はNext.jsを用いて編集フォームを作成したのですが、
苦労したところ、学んだところがたくさんありましたので、
紹介させてください。

作成した編集フォームの概要

Todoアプリを考えてもらって、3つのテーブルが存在します。
これらのテーブルを編集するためのフォームを作成していきます。

1:Todoのタイトル、概要を保持するテーブル
2:Todoリストの各todoの情報を格納するテーブル
3:各todoの詳細のdetail_todoの情報を格納するテーブル

1:Todoテーブル

id タイトル 概要
1 Pythonを勉強する アルゴリズムを学習するための準備としてPythonを学習する
2 Railsを学ぶ Webアプリの構築のために

2:Taskテーブル

id Task Todo_id
1 Pythonの基本文法を学ぶ 1
2 Pythonでclassを作成する 1
3 Rails環境を整える 2

3:Detail_taskテーブル

id detail_task task_id
1 printで出力を行う 1
2 if文を学ぶ 1
3 loopを学ぶ 1

バックエンド(Rials)開発

todos_controller.rb
class TodosController < ApplicationController

    def update
     #編集対象のTodoをidで検索
      @todo = Todo.find_by(id: params[:id])

      if @todo.update(todo_params)
         render json: @todo, status: :ok # ステータスコードを適切に変更
      else
         render json: { message: '更新に失敗しました', errors: @todo.errors.full_messages }, status: :unprocessable_entity
      end
    end
  #パラメータ制御
  private
      #Postで送信されてくるに不要なものがないのか制御する
      def todo_params
        params.require(:todo).permit(:title, :description, tasks_attributes: [
          :id, :title, :_destroy, detail_tasks_attributes: [
            :id, :title, :_destroy
          ]
        ])
      end  
routes.rb
post 'todos/:id/edit',    to: 'todos#update'
モデル
class Todo < ApplicationRecord
  has_many :tasks, dependent: :destroy

  #子テーブルに当たるtasksモデルの更新とTodo.updateだけで行えるような設定
  accepts_nested_attributes_for :tasks, allow_destroy: true
end

class Task < ApplicationRecord
  belongs_to :todo
  has_many :detail_tasks, dependent: :destroy 
  accepts_nested_attributes_for :detail_tasks, allow_destroy: true
end

class DetailTask < ApplicationRecord
  belongs_to :task
  has_many :user_statuses, dependent: :destroy
end

編集Fromの概要

編集Fromは大きく2つに分割されており、「TodoとTaskを編集するフォーム」、「DetailTaskを編集するフォーム」がある。

今回の記事では、「TodoとTaskを編集するフォーム」についてのみ解説する

1:Todoテーブル

id タイトル 概要
1 Pythonを勉強する アルゴリズムを学習するための準備としてPythonを学習する
2 Railsを学ぶ Webアプリの構築のために

2:Taskテーブル

id Task Todo_id
1 Pythonの基本文法を学ぶ 1
2 Pythonでclassを作成する 1
3 Rails環境を整える 2

以上のテーブルのデータを編集するフォームになる

まず、最初にページをレンダリングすると

const { id } = useParams();

によって、idの値が更新されることで

useEffect(() => {
    (省略 ※下に全コードを載せています)
    catchdatas();
  }, [id]);

useEffectの中のcatchdatas関数が実行され、
バックエンドから更新対象のデータを抽出する

それぞれのデータは
「todoTitle」、「todoDescription」「todos」に格納され、それぞれのuseStateで管理される

  const [todos, setTodos] = useState([]);
  const [todoTitle, setTodoTitle] = useState('');
  const [todoDescription, setTodoDescription] = useState('');

Todoテーブルのデータ[todoTitle][todoDescription]は

    <input onChange={(e) => setTodoTitle(e.target.value)}/>

でinputに入力すれば即データが更新されるようになっている

Taskテーブルのデータは、
addTodos、removeItem関数で管理し
addTodosは「追加」ボタンをクリックすると実行され
からデータがtodosに追加される

removeItem関数は
特定のindexの_destroyをtrueにし、バックエンドに送ることで削除

ちなみに、送信しないと削除されないため、送信前に削除したように見せるため以下のように工夫

{todos.filter(todo => !todo._destroy).map((todo, index) => (
 (省略)
))}

その他の、

編集From1:TodoとTaskを編集するフォーム

frontend\app\guidelines[id]\page.tsx
// useStateなどCSRを行うため記述
'use client'; 

// のちに解説するdetail_taskを編集するフォーム
import Detailsform from '@/app/Components/Form/Detailtasks/page'; 

// useStateでデータを管理
import { useState,useEffect } from 'react';

// axiosでバックエンドにHTTP送信を行うため
import axios from "axios";

// Next.js内でのページ遷移を実行するため
import { useRouter } from 'next/navigation';

// URLの[id]の値を取得するため
import { useParams } from 'next/navigation';

// TodoとTaskを編集フォームを作成
const Form = () => {

  //URLの[id]の値をidという変数に格納
  const { id } = useParams();
  
 //「isDetailVisible」という変数でdetail_taskを編集するためのフォームを表示・非表示を切り替える
  const [isDetailVisible, setDetailVisible] = useState(false);

  // 現在、編集対象のTodoのid
  const [currentTodoIndex, setCurrentTodoIndex] = useState(null);

  //Todo,Taskテーブルのデータを管理
  const [todos, setTodos] = useState([]);
  //Todoテーブルの[タイトル]のデータを管理
  const [todoTitle, setTodoTitle] = useState('');
  //Todoテーブルの[概要]のデータを管理
  const [todoDescription, setTodoDescription] = useState('');

  //ページ遷移を行うため、useRouterをインスタンス化
  const router = useRouter();

  //「todos」に空データを挿入するための関数
  const addTodos = () => {
    const newTodo = { title: '', detail_tasks: [], _destroy: false };
    setTodos([...todos, newTodo]);
  };

 //「todos」に対してindexにも基づいてデータを削除するための関数
  const removeItem = (index) => {
    const newTodos = [...todos];
    if (newTodos[index].id) {
      newTodos[index]._destroy = true; 
    } else {
      newTodos.splice(index, 1); 
    }
    setTodos(newTodos);
  };

  // indexを更新し、DetailVisibleをTrueにしサブフォームを表示させるための関数
  const showDetail = (index) => {
    setCurrentTodoIndex(index);
    setDetailVisible(true);
  };

 // サブフォームを非表示にするための関数
  const closeDetail = () => {
    setDetailVisible(false);
    setCurrentTodoIndex(null);
  };

  // Todoテーブルのタイトルを更新するための関数
  const handleTitleChange = (index, newTitle) => {
    const newTodos = [...todos];
    newTodos[index].title = newTitle;
    setTodos(newTodos);
  };

 // サブフォームで編集した内容を更新する関数
  const updateTaskDetails = (index, details) => {
    const newTodos = [...todos];
    newTodos[index].detail_tasks = details;
    setTodos(newTodos);
  };

 // 「todo」を作成して、バックエンドに送信
  const Submit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    try {
      const todo = {
        title: todoTitle, 
        description: todoDescription, 
        tasks_attributes: todos.map(todo => ({
          id: todo.id, 
          title: todo.title,
          _destroy: todo._destroy,
          detail_tasks_attributes: todo.detail_tasks.map(detail => ({
            id: detail.id,
            title: detail.detailtitle,
            _destroy: detail._destroy 
          }))
        }))
      };
      const response = await axios.post(`http://127.0.0.1:3001/todos/${id}/edit`, 
        { todo }
      );
      alert("更新できました");
      router.push('/');
    } catch (error) {
      console.error('投稿に失敗しました', error);
    }
  };

 //idが変更するたびにバックエンドからデータを再取得
  useEffect(() => {
    const catchdatas = async () => {
      try {
        const response = await axios.get(`http://127.0.0.1:3001/todos/${id}`);
        const todo = response.data;
        setTodoTitle(todo.title);
        seTodooDescription(todo.description);
        setTodos(todo.tasks.map(task => ({
          id: task.id,
          title: task.title,
          _destroy: false, 
          detail_tasks: task.detail_tasks.map(detail => ({
            id: detail.id,
            detailtitle: detail.title,
            _destroy: false // Initialize _destroy flag
          }))
        })));
      } catch (error) {
        console.error('データの取得に失敗しました', error);
      }
    };
    catchdatas();
  }, [id]);

  //対象のTodoを削除
  const handleDelete = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (window.confirm("本当にこのガイドラインを削除しますか?")) {
      try {
        const response = await axios.delete(`http://127.0.0.1:3001/todos/${id}`);
        if (response.status === 200) {
          alert("ガイドラインが正常に削除されました");
          router.push('/');
        } else {
          alert("ガイドラインの削除に失敗しました");
        }
      } catch (error) {
        console.error('データの削除に失敗しました', error);
        alert("ガイドラインの削除中にエラーが発生しました");
      }
    }
  };

  return (
    <div className='mx-auto w-6/12 mt-6'>

      //
      {isDetailVisible && currentTodoIndex !== null ? (
        <Detailsform
         //サブフォームを切り替えるための関数
          closeDetail={closeDetail}
         //サブフォームで更新するデータ
          todo={todos[currentTodoIndex]}
         //更新対象のindex 
          index={currentTodoIndex}
         //detailtaskの内容を反映させるための関数 
          updateTodoDetails={updateTodoDetails}
        />
      ) : (
      
        <form >
        
          <button type="button" onClick={handleDelete} className='px-4 py-2 bg-red-500 border rounded-lg'>削除</button>
          <div id="open">
            <div className='w-full'>
              <label className='mt-4 font-bold mr-4'>タイトル</label>
              <input 
                type="text" 
                className="w-full border px-4 py-2"
                value={guidelineTitle}
                ??
                onChange={(e) => setTodoTitle(e.target.value)}
              />
            </div>

            <div className='w-full mt-4'>
              <label className='mt-4 font-bold mr-4'>概要</label>
              <input 
                type="text" 
                className="w-full border px-4 py-2"
                value={guidelineDescription}
                onChange={(e) => setTodoDescription(e.target.value)}
              />
            </div>

            <div className='w-full mt-4'>
              <label className='mt-4 font-bold mr-4'>Todolist</label>
              
              {todos.filter(todo => !todo._destroy).map((todo, index) => (
                <div className="w-full" id="target" key={index}>
                  <div className="flex items-center gap-1 mb-2 mt-1">
                    <input 
                      type="text" 
                      className="w-10/12 border py-2 rounded-lg"
                      value={todo.title}
                      onChange={(e) => handleTitleChange(index, e.target.value)}
                    />
                    <button type="button" onClick={() => showDetail(index)} className='px-2 py-2 border-2 rounded-lg bg-blue-700 text-white font-bold'>
                      詳細
                    </button>
                    <button type="button" onClick={() => removeItem(index)} className='px-2 py-2 border-2 rounded-lg bg-red-700 text-white font-bold'>
                      削除
                    </button>
                  </div>
                </div>
              ))}
            </div>

            <button type="button" onClick={addTodos} className='mt-1 font-bold px-2 py-2 rounded-md bg-blue-700 text-white'>
              追加
            </button>

            <button onClick={Submit} type="submit" className='px-2 py-2 border-2 rounded-lg bg-blue-700 text-white font-bold mt-1'>
              登録
            </button>
          </div>
        </form>
      )}
    </div>
  );
}

export default Form;

まとめ

Next.jsで編集フォームを作成する方法をまとめました。
useEffectを用いてデータ取得することや、useStateでデータを管理するを再確認することができてよかったです。

また、データ削除のために
「_destroy」をRialsに送信することが必要で、
Next.js側では配列を用いてデータ管理する基本を確認

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?