タスク管理ツールをReact + ASP.Coreで作る 014詳細画面(削除)

[前回](https://qiita.com/mosuma/items/f3aefbe0adc5dc752d68)の続きです

今回から、登録済みタスクの削除機能を作っていこうと思います

### ガイド
全体Index：[タスク管理ツールをReact + ASP.Coreで作る - Index](https://qiita.com/mosuma/items/88e40dc44e7bff1a54d8)

## 流れ
構築の流れは以下になります
- コントローラーを修正してサーバーサイドに削除用のapiを作成
- クライアントに削除機能を追加


## コントローラーを修正してサーバーサイドに削除用のapiを作成

まずコントローラーに、削除用の入力を受け取る関数を定義します。
削除したいタスクのIDを取得したらそれをDBから削除し、結果を返す内容です。

```diff_c_sharp:TaskController.cs


+        [HttpPost("delete/{id}")]
+        public async Task<ActionResult<bool>> Delete(Guid id)
+        {
+            var task = await _context.t_tasks.FindAsync(id);
+
+            if(task == null) return null;
+
+            _context.Remove(task);
+
+            var result = await _context.SaveChangesAsync()>0;
+
+            return result;
+        }
    }
}
```


<details><summary>参考：TaskController.cs全体(開くと表示)</summary><div>



```diff_c_sharp:TaskController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using server_app.Models.EDM;

namespace server_app.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class TaskController : ControllerBase
    {
        private readonly DataContext _context;
        public TaskController(DataContext context)
        {
            _context = context;
        }
        [HttpGet]
        public async Task<ActionResult<List<t_task>>> Get()
        {
            return await _context.t_tasks.ToListAsync();
        }
        [HttpGet("{id}")]
        public async Task<ActionResult<t_task>> Details(Guid id)
        {
            return await _context.t_tasks.FindAsync(id);
        }
        [HttpPost("update")]
        public async Task<ActionResult<t_task>> Update([FromBody] t_task task)
        {
            var temp = await _context.t_tasks.FindAsync(task.id_task);
            temp.title = task.title;
            temp.is_finish = task.is_finish;
            temp.description = task.description;
            temp.end_date_scheduled = task.end_date_scheduled;
            temp.end_date_actual = task.end_date_actual;

            await _context.SaveChangesAsync();

            return await _context.t_tasks.FindAsync(task.id_task);
        }
        [HttpPost("create")]
        public async Task<ActionResult<t_task>> Create([FromBody] t_task task)
        {
            var new_task = new t_task
            {
                id_task = task.id_task,
                title = task.title,
                is_finish = task.is_finish,
                description = task.description,
                end_date_scheduled = task.end_date_scheduled,
                end_date_actual = task.end_date_actual,
            };

            await _context.t_tasks.AddAsync(new_task);
            await _context.SaveChangesAsync();

            return await _context.t_tasks.FindAsync(task.id_task);
        }
        [HttpPost("delete/{id}")]
        public async Task<ActionResult<bool>> Delete(Guid id)
        {
            var task = await _context.t_tasks.FindAsync(id);

            if(task == null) return null;

            _context.Remove(task);

            var result = await _context.SaveChangesAsync()>0;

            return result;
        }
    }
}
```


</div></details>





## クライアントの仕様検討

### 基本動作

クライアント側の動作は以下のようになります。

- 既存レコード編集モードの場合は「TaskEdit」画面に削除ボタンを出す
- ボタンを押したらapiに削除を問い合わせる
- 削除が成功したら何も選択しないモードになる

これまでのタスク追加や編集でもそうなのですが、「削除が成功した」などのメッセージがなく、結果が分からず不親切な動作になっているのが今更気になります。
それらについては一通り作り終えた後に検討していこうと思います。



## クライアント側の追加パッケージ
今回はありません



## クライアントサイドのコード変更

コードを変更していきます。

### TaskOperationMain
「TaskEdit」を呼ぶときに「setSelectedId_task」も渡すようにしました。

これによって、削除成功後にselectedId_taskを空にすることで編集画面をいったんクリアすることが目的です。

```diff_tsx:TaskOperationMain.tsx
+                            <TaskEdit isModeAddnew={isModeAddnew} id_task={selectedId_task} setSelectedId_task={setSelectedId_task} />
```

### TaskEdit
削除ボタン・削除ボタン押下時用にデータ検証、apiの呼び出しを追加しました

```diff_tsx:TaskEdit.tsx
import { Form, Formik } from "formik";
import { useEffect, useState } from "react";
import { Col, Row } from "react-bootstrap";
import * as Yup from 'yup';
import CheckBoxGeneral from "../app/common/CheckBoxGeneral";
import DateInputGeneral from "../app/common/DateInputGeneral";
import TextAreaGeneral from "../app/common/TextAreaGeneral";
import TextInputGeneral from "../app/common/TextInputGeneral";
import {v4} from 'uuid';

interface Task {
    id_task: string;
    title: string;
    is_finish: boolean;
    description: string;
    end_date_scheduled: Date | null;
    end_date_actual: Date | null;
  }


interface Props {
    isModeAddnew: boolean;
    id_task: string;
+   setSelectedId_task: React.Dispatch<React.SetStateAction<string>>;
}

+export const TaskEdit = ({isModeAddnew, id_task, setSelectedId_task}: Props) => {    
    
    const [task, setTask] = useState<Task>();

    useEffect(() => {
        if(id_task !== ""){
            loadTaskDetails();
        } else {
            setTask({id_task : "", title : "", is_finish: false, description:"", end_date_scheduled : null, end_date_actual : null})
        }
    }, [id_task]);
  
    const loadTaskDetails = async () => {
        const response = await fetch(`https://localhost:5001/task/${id_task}`);
        const data = await response.json();
        setTask(data);
    };


    const updateTaskDetails = async (value : Task) => {

        if(value.id_task===""){
            const newTask = value;
            newTask.id_task=v4();

            const response = await fetch("https://localhost:5001/task/create", { 
                method: "POST",
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(newTask)  
            });
            const data = await response.json();
            setTask(data);

        } else {
            const response = await fetch("https://localhost:5001/task/update", { 
                method: "POST",
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(value)  
            });
            const data = await response.json();
            setTask(data);
        }
    };


+    const deleteTask = async (value : Task) => {
+
+        if(value.id_task!==""){
+
+            const response = await fetch(`https://localhost:5001/task/delete/${value.id_task}`, { 
+                method: "POST",
+            });
+            const data = await response.json();
+            setSelectedId_task("");
+
+        }
+    };
    
    const validationSchema = Yup.object({
        title: Yup.string().required(),
        is_finish: Yup.bool().required(),
        description: Yup.string().nullable(),
        end_date_scheduled: Yup.date().nullable(),
        end_date_actual: Yup.date().nullable(),
    });
    
+    const validationSchemaDel = Yup.object({
+        id_task: Yup.string().required(),
+    });



    return (
        <div>
            {
                isModeAddnew ?
                    <h3>Add New Task</h3>
                    :
                    <h3>Task Detail : {task?.title}</h3>
            }
            { task &&
            <div>
                <Formik
                validationSchema={validationSchema}
                enableReinitialize 
                initialValues={task} 
                onSubmit={values => updateTaskDetails(values)}>
                {({ handleSubmit, isValid, isSubmitting, dirty }) => (
                    <Form className="ui form" onSubmit = {handleSubmit} autoComplete='off'>

                        <Row>
                            <Col><TextInputGeneral label='Title' name='title' placeholder='Title' /></Col>
                        </Row>

                        <hr />
                        
                        <Row>
                            <Col ><TextAreaGeneral label='Description' placeholder='Description' name='description' rows={3}   /></Col>
                        </Row>
                        
                        <Row>
                            <Col ><DateInputGeneral placeholderText='Due Date' name = 'end_date_scheduled' dateFormat='MM d, yyyy' /></Col>
                            <Col ><DateInputGeneral placeholderText='Completion Date' name = 'end_date_actual' dateFormat='MM d, yyyy' /></Col>
                        </Row>


                        
                        <Row>
                            <Col xs={4}><CheckBoxGeneral label='Finished' name='is_finish'  /></Col>
                        </Row>
                        
                        <hr />
                        
                        <button disabled={!isValid || !dirty || isSubmitting} type = 'submit' className='btn btn-primary'>
                            {isSubmitting ? "Processing" : "submit"}
                        </button>
                    </Form>
                )}

                </Formik>

+                {
+                !isModeAddnew &&
+                <Formik
+                validationSchema={validationSchemaDel}
+                enableReinitialize 
+                initialValues={task} 
+                onSubmit={values => deleteTask(values)}>
+                {({ handleSubmit, isValid, isSubmitting, dirty }) => (
+                    <Form className="ui form" onSubmit = {handleSubmit} autoComplete='off'>                        
+                        <button disabled={!isValid || isSubmitting} type = 'submit' className='btn btn-danger'>
+                            {isSubmitting ? "Processing" : "Delete"}
+                        </button>
+                    </Form>
+                )}
+                </Formik>
+                }
            </div>

            }
        </div>

        

    )
}

```



変更は以上です

## 動作させてみる

上記の状態で、サーバー・クライアント双方を更新すると、以下の様になります。
<iframe width="560" height="315" src="https://www.youtube.com/embed/NdPUKeUdrk4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

今回は以上です。
続きは[次回](https://qiita.com/mosuma/items/75d387607cebdfc22bee)です