前回の続きです
概要
016~前回までで、認証機能の組み込み、ログイン、ログイン状態かそうでないかに従って表示制御を行う機能が一通りできました。
一方ここまで色々作りが荒い部分もあるので、一度リファクタリングでコード整理をしていきたいと思います。
具体的には、ログイン・ユーザー登録の部分でfetch関数を使った各コンポーネントでの直書きの実装になっているので、APIに問い合わせる部分をapi.tsに組込み、ログイン・ユーザー登録のコンポーネントではそれを呼び出す実装に変更します。
それでは構築していきましょう。
ガイド
全体Index:タスク管理ツールをReact + ASP.Coreで作る - Index
認証機能追加の一連の流れ
- 016サーバーサイドに認証機能関連のクラス追加
- 017クライアントにユーザー登録・ログイン画面と処理を追加
- 018認証トークン関連機能をサーバー・クライアント双方に追加
- 019認証有無に従い制御を実施する機能を追加
- [020認証機能をリファクタリング(本記事)]
- 021認証状態に従い画面表示を切り替え
構築
リファクタリングです。
ログイン回りの機能が、api等基本構成のままで作っているので、タスク関連と同様に
- モデル追加
- api.ts修正:ログイン関連の処理を追加
- Ligin.tsx修正:API問い合わせをapi.tsのモジュールに変更、入力欄をFormik+共用入力部品に変更
- Register.tsx修正:API問い合わせをapi.tsのモジュールに変更、入力欄をFormik+共用入力部品に変更
モデル追加
APIとの通信を部品化するにあたり、まずモデルクラスを追加します。
export interface UserInfo {
username: string;
email: string;
token: string;
}
export interface UserFormValues {
email: string;
password: string;
username?: string;
}
API変更
api.tsを修正します。上で作ったモデルクラスを使用して、APIとやり取りを行う処理を追加します。
import axios, { AxiosResponse } from "axios";
+ import { UserFormValues, UserInfo } from "../models/Account";
import { Task } from "../models/Task";
axios.defaults.baseURL = "https://localhost:5001";
axios.interceptors.request.use(config => {
const token = window.localStorage.getItem('tasket_jwt_token');
if(token) config.headers!.Authorization = `Bearer ${token}`
return config;
})
+ const Account = {
+ login: (user: UserFormValues) => axios.post<UserInfo>(`/account/login`, user).then((response: + AxiosResponse<UserInfo>)=>response.data),
+ register: (user: UserFormValues) => axios.post<UserInfo>(`/account/register`, user).then((response: AxiosResponse<UserInfo>)=>response.data),
+ }
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 = {
+ Account,
Tasks,
}
export default api;
ログイン・ユーザー登録コンポーネントの変更
それぞれのコンポーネントを修正します。
具体的には、サーバーサイドとの通信を先ほど作ったapi.tsの関数を使用する様に変更、各入力フォームをFormik+Yupと、各入力用部品類の組み合わせに変更します。
大半が書き直しになっているので差分ではなく全体を記載しています。
import { ErrorMessage, Formik } from 'formik';
import React from 'react';
import { Form } from 'react-bootstrap';
import * as Yup from 'yup';
import api from '../app/api/api';
import TextInputGeneral from '../app/common/TextInputGeneral';
const Login = (
) =>
{
return (
<>
<Formik
initialValues={{email:'', password: '', error: null}}
onSubmit={async (values, {setErrors}) => {
const content = await api.Account.login(values).catch(error =>
setErrors({error:'Invalid email or password'}));
content?.token && window.localStorage.setItem('tasket_jwt_token', content.token);
}
}
validationSchema={Yup.object({
email: Yup.string().required().email(),
password: Yup.string().required(),
})}
>
{({handleSubmit, isSubmitting, errors, isValid, dirty}) =>(
<Form className="ui form" onSubmit={handleSubmit} autoComplete='off'>
<h3>Login</h3>
<TextInputGeneral name='email' placeholder="Email" />
<TextInputGeneral name='password' placeholder="Password" type="password" />
<ErrorMessage
name='error' render={() =>
<Form.Label style = {{marginBottom:10}} basic color='red' >{errors.error}</Form.Label>
}
/>
<button disabled={!isValid || !dirty || isSubmitting} type = 'submit' className="btn btn-primary">Login</button>
</Form>
)}
</Formik>
</>
);
}
export default Login;
import { ErrorMessage, Formik } from 'formik';
import React from 'react';
import { Form, ListGroup } from 'react-bootstrap';
import TextInputGeneral from '../app/common/TextInputGeneral';
import * as Yup from 'yup';
import api from '../app/api/api';
const Register = () => {
return (
<>
<Formik
initialValues={{username: '', email:'', password: '', error: null}}
onSubmit={(values, {setErrors}) =>
api.Account.register(values).catch(error =>
setErrors({error}))
}
validationSchema={Yup.object({
username: Yup.string().required(),
email: Yup.string().required().email(),
password: Yup.string().required(),
})}
>
{({handleSubmit, isSubmitting, errors, isValid, dirty}) =>(
<Form className="ui form error" onSubmit={handleSubmit} autoComplete='off'>
<h3>Sigh up to Tasket</h3>
<TextInputGeneral name='username' placeholder="User Name" />
<TextInputGeneral name='email' placeholder="Email" />
<TextInputGeneral name='password' placeholder="Password" type="password" />
<ErrorMessage
name='error' render={() =>
<ValidationErrors errors = {errors.error} />}
/>
<button disabled={!isValid || !dirty || isSubmitting} type = 'submit' className="btn btn-primary">Submit</button>
</Form>
)}
</Formik>
</>
);
}
export default Register;
interface Props {
errors: any;
}
function ValidationErrors({errors}: Props){
return (
<>
{errors && (
<ListGroup>
{errors.map((err: any, i: any) => (
<ListGroup.Item key = {i} >{err}</ListGroup.Item>
))}
</ListGroup>
)}
</>
)
}
動作させてみる
各入力欄のUIが改善されました。
また内部のコードも多少整理されました。
(今回は動作自体は前回と大きくは変わりません。)
今回は以上です。
続きは次回です