0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

タスク管理ツールをReact + ASP.Coreで作る 012詳細画面(編集機能)

Last updated at Posted at 2022-11-01

前回の続きです

ガイド

全体Index:タスク管理ツールをReact + ASP.Coreで作る - Index

流れ

  • クライアント側のフォルダ整理
  • 入力フォームをFormikで構築

クライアント側のフォルダ整理

ファイルが増えてきたので、フォルダ構成を一度整理しました。
Reactの各ページを構成するようなコンポーネント類を「components」、それ以外の処理関連や、共通して使うようなReactコンポーネントを「app」に配置するという構成です。

← Before | After →
image.png

クライアント側にパッケージのインストール

ここまであまり個々の機能に専用パッケージみたいなものは入れず、極力クリーンアーキテクチャーでやってきましたが、この先全部自前は逆に大変そうなので、入力関連はパッケージを使っていこうと思います。

以下をインストールします。

npm install formik react-datepicker @types/react-datepicker yup

Reactで入力フォームを使う際に使用事例が多いformik、データ検証を可能にするYup、日付操作機能を簡単に実装するためのreact-datepickerを導入しました。

コードの変更

コードを変更していきます。
今回は若干変更量多いです。

サーバーサイド

コントローラーに、更新用の入力を受け取る関数を定義します

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);
+        }
    }
}

入力用の部品

次は入力用の部品たちです。
Formにすべての定義を書くと長くなるので、データの型ごとに入力フォーム用の部品を作っておきます。

CheckBoxGeneral.tsx
import { useField } from "formik";
import { Form } from 'react-bootstrap'

interface Props{
    name:string;
    label?: string;
}

export default function CheckBoxGeneral(props: Props){
    const[field, meta] = useField({ name: props.name, type: "checkbox" }
        );
    return (
        <>
            <Form.Check {...field} type='checkbox' label={props.label} />
            {meta.touched && meta.error ? (
                <Form.Label>{meta.error}</Form.Label>
            ) : null}
        </>
    )
}
DateInputGeneral.tsx

import { useField } from "formik";
import React from "react";
import { Form } from "react-bootstrap";
import DatePicker, {ReactDatePickerProps} from 'react-datepicker';

export default function DateInputGeneral(props: Partial<ReactDatePickerProps>){
    const[field, meta, helpers] = useField(props.name!);
    return (
        <Form.Group>
            <DatePicker
                {...field}
                {...props}
                selected={(field.value && new Date(field.value)) || null}
                onChange={value => helpers.setValue(value)}
            />
            {meta.touched && meta.error ? (
                <Form.Label basic color='red'>{meta.error}</Form.Label>
            ) : null}
        </Form.Group>
    )
}

TextAreaGeneral.tsx

import { useField } from "formik";
import { Form } from 'react-bootstrap'

interface Props{
    placeholder: string;
    name:string;
    rows:number;
    label?: string;
}

export default function TextAreaGeneral(props: Props){
    const[field, meta] = useField(props.name);
    return (
        <>
        <Form.Group>
            { props.label && <Form.Label>{props.label}</Form.Label> }
            <Form.Control as="textarea" {...field} {...props} />
            {meta.touched && meta.error ? (
                <Form.Label>{meta.error}</Form.Label>
            ) : null}
        </Form.Group>
        </>
    )
}

TextInputGeneral.tsx

import { useField } from "formik";
import { Form } from 'react-bootstrap'

interface Props{
    placeholder: string;
    name:string;
    type?: string;
    label?: string;
    disabled?: boolean;
}

export default function TextInputGeneral(props: Props){
    const[field, meta] = useField(props.name);
    return (
        <>
        <Form.Group>
            { props.label && <Form.Label>{props.label}</Form.Label> }
            <Form.Control {...field} {...props} />
            {meta.touched && meta.error ? (
                <Form.Label>{meta.error}</Form.Label>
            ) : null}
        </Form.Group>
        </>
    )
}

TaskEditの変更

TaskEditは、前回まではEditといいつつ表示のみでしたが、今回で既存レコードの編集機能を実装した状態になりました。
変更点が多いので差分ではなく変更後の状態を載せてあります。

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";

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


interface Props {
    id_task: string
}

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

    useEffect(() => {
        id_task !== "" && loadTaskDetails();
    }, [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) => {
        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 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(),
    });



    return (
        <div>
            <h3>Task Detail : {task && 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='Short Description' placeholder='Description' name='short_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>

            </div>

            }
        </div>

        

    )
}

Index.tsxに、react-datepickerのcssを呼びます。
(これを呼ばないと、日時選択画面がうまく動作しない)

Index.tsx
import App from './App';
import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.min.css';
+ import 'react-datepicker/dist/react-datepicker.css';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement

変更後のフォルダの全体像は以下です

image.png

動作させてみる

上記の状態で、サーバー・クライアント双方を更新すると、以下の様になります。

ブラウザを更新しても入力値が残っているので、各レコードを操作することが可能になっているのが分かります。

一方で日付に対してスタイルが適切に当たっていなかったり、右側の詳細エリアの更新結果が左側のリストには反映されていない(詳細側を更新しても期日やfinishフラグが更新されていない)など、修正を要する点があることも併せて分かりました。
これらも今後引き続き作りこみながら直していこうと思います。

今回は以上です。
続きは次回です

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?