前回の続きです
今回は機能のリファクタリングとして、Axiosによって通信機能の記述を簡略化してみます。
ガイド
全体Index:タスク管理ツールをReact + ASP.Coreで作る - Index
概要
これまでAPIとの通信はfatch関数で自前処理していました。
一方で、今後エラー発生時のハンドリング・認証トークンの追加などの機能を組み込んでいくにあたり、全部自前処理するよりもライブラリを活用した方が手間が少そうなため、APIとの通信はAxiosを使用していくことにしました。
今回の流れは以下です
- Axiosのインストール
- API問い合わせ機能を構築
- データモデル用の型を切り出す
- 各コンポーネントでのAPIとの通信を変更
Axiosのインストール
初めにパッケージをインストールします。
axiosの本記事記載時点での最新バージョンは1が出ていますが、ネットに情報が少ないことと、筆者自身が0.26シリーズに慣れていることもあり、0.26を指定してインストールします。
> npm install axios@^0.26.1
新規にAPIと通信する機能を作る
これまで各コンポーネントに記載してきたtaskオブジェクトに対するCURD処理をAxiosに置き換えます
import axios, { AxiosResponse } from "axios";
import { Task } from "../models/Task";
axios.defaults.baseURL = "https://localhost:5001";
const Tasks = {
index: () => axios.get<Task[]>(`/task`).then((response: AxiosResponse<Task[]>)=>response.data),
details: (id:string) => axios.get<Task>(`/task/${id}`).then((response: AxiosResponse<Task>)=>response.data),
create: (task:Task) => axios.post<Task>(`/task/create`, task).then((response: AxiosResponse<Task>)=>response.data),
update: (task:Task) => axios.post<Task>(`/task/update`, task).then((response: AxiosResponse<Task>)=>response.data),
delete:(id:string) => axios.post<void>(`/task/delete/${id}`),
}
const api = {
Tasks,
}
export default api;
データモデル用の型を切り出す
これまでデータモデルはコンポーネントごとに個別に記載していましたが、同じものを個別に書いていて冗長であると同時に、仕様変更時に一部を変え忘れてバグの元になる可能性があったので、今回を機にモデル用のクラスとして切り出し、ほかの関数ではこれを呼び出す様に変更しました。
export interface Task {
id_task: string;
title: string;
is_finish: boolean;
description: string;
end_date_scheduled: Date | null;
end_date_actual: Date | null;
}
各コンポーネントでのAPIとの通信を変更
基本動作
import { useEffect, useState } from 'react';
import { Button, Table } from 'react-bootstrap';
+import api from '../app/api/api';
+import { Task } from '../app/models/Task';
interface Props {
selectedId_task: string;
setIsModeAddnew: React.Dispatch<React.SetStateAction<boolean>>;
setSelectedId_task: React.Dispatch<React.SetStateAction<string>>;
}
export const TaskList = ({setIsModeAddnew, selectedId_task, setSelectedId_task}: Props) => {
const [loading, setLoading] = useState(true);
const [tasks, setTasks] = useState<Task[]>();
useEffect(() => {
populateWeatherData();
}, []);
const populateWeatherData = async () => {
+ const data = await api.Tasks.index();
setTasks(data);
setLoading(false);
};
if(loading) return <div>loading....</div>
return (
<div>
<h1 id="tabelLabel">Task List</h1>
<p>This component demonstrates fetching data from the server.</p>
<Table >
<thead>
<tr>
<th>No.</th>
<th>Fin.</th>
<th>Title</th>
<th>Due Date</th>
</tr>
</thead>
<tbody>
{tasks && tasks.map((task, index) => (
<tr
key={task.id_task} onClick={()=>{setIsModeAddnew(false); setSelectedId_task(task.id_task);}}
className={ selectedId_task === task.id_task ? "table-info" : ""}
>
<td>{index+1}</td>
<td><input type="checkbox" defaultChecked={task.is_finish} disabled /></td>
<td>{task.title}</td>
<td>{task.end_date_scheduled && (new Date(task.end_date_scheduled).toDateString())}</td>
</tr>
))}
</tbody>
</Table>
</div>
)
}
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';
+import api from "../app/api/api";
+import { Task } from "../app/models/Task";
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 data = await api.Tasks.details(id_task);
setTask(data);
};
const updateTaskDetails = async (value : Task) => {
if(value.id_task===""){
const newTask = value;
newTask.id_task=v4();
+ const data = await api.Tasks.create(newTask);
setTask(data);
} else {
+ const data = await api.Tasks.update(value);
setTask(data);
}
};
const deleteTask = async (value : Task) => {
if(value.id_task!==""){
+ const data = await api.Tasks.delete(value.id_task);
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>
)
}
変更は以上です
動作させてみる
前回と同じ内容で動作します
今回は以上です。
続きは次回です
TAG:V000-12-00