概要
前回作成した【Django】GraphQLのJWT認証を構築を利用してフロントエンド側にログイン画面の作成を紹介します。
画面はシンプルにユーザ名とパスワードのみとし、認証に成功した場合ローカルストレージにトークン類を保存して遷移するようにします。
入力Formにreact-hooks-form
、クライアントにApolloClient
、画面遷移にreact-router
を使用します。
ライブラリバージョン
- typescript 4.0.3
- react 17.0.1
- @apollo/client 3.2.9
- graphql 15.4.0
ライブラリインストール
$ yarn install @apollo/client graphql
ソースコード
ApolloClientの作成
http://127.0.0.1:8000/graphql/
に接続するクライアントを作成します。
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
const client = new ApolloClient({
uri: 'http://127.0.0.1:8000/graphql/',
cache: new InMemoryCache(),
});
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root'),
);
カスタムhooksの作成
下記3つを返却する認証用hooksを作成します。
-
authentication
・・・認証が成功したかを返却する非同期関数。成功した場合はローカルストレージにトークン類を保存する。 -
loading
・・・ローディング中を示すbool値 -
error
・・・エラーの状態を示すオブジェクト
import { useCallback } from 'react';
import { useMutation, gql, ApolloError } from '@apollo/client';
interface Payload {
username: string;
exp: number;
origIat: number;
}
interface TokenAuth {
tokenAuth: {
token: string;
payload: Payload;
refreshToken: string;
refreshExpiresIn: number;
};
}
const TOKEN_AUTH = gql`
mutation tokenAuth($username: String!, $password: String!) {
tokenAuth(username: $username, password: $password) {
token
payload
refreshToken
refreshExpiresIn
}
}
`;
// 画面上の入力に使用
export interface Inputs {
username: string;
password: string;
}
interface Return {
authentication: (inputs: Inputs) => Promise<boolean>;
loading: boolean;
error: ApolloError | undefined;
}
export const useAuthentication = (): Return => {
const [tokenAuth, { loading, error }] = useMutation<TokenAuth, Inputs>(
TOKEN_AUTH,
);
const authentication = useCallback(
async (inputs: Inputs): Promise<boolean> => {
try {
const result = await tokenAuth({
variables: {
username: inputs.username,
password: inputs.password,
},
});
if (result.data !== null && result.data !== undefined) {
localStorage.setItem('token', result.data.tokenAuth.token);
localStorage.setItem(
'refreshToken',
result.data.tokenAuth.refreshToken,
);
localStorage.setItem(
'refreshExoireIn',
result.data.tokenAuth.refreshExpiresIn.toString(),
);
return true;
}
return false;
} catch (autherror) {
console.error(autherror);
return false;
}
},
[tokenAuth],
);
return { authentication, loading, error };
};
ログイン画面の作成
ユーザ名とパスワードを入力して、認証に成功した場合に、Account/mypage
に遷移します。
認証結果が返ってくるまでの読み込み中は、Loading...
が表示されるようになっています。
import { useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';
import React, { FC, useEffect } from 'react';
import { Inputs, useAuthentication } from 'hooks/useAuthentication';
const Login: FC = () => {
const history = useHistory();
const {
register,
handleSubmit,
reset,
errors,
formState: { isSubmitSuccessful },
} = useForm<Inputs, React.BaseSyntheticEvent>();
const { authentication, loading, error } = useAuthentication();
const onSubmit = async (input: Inputs, e?: React.BaseSyntheticEvent) => {
if (e !== undefined) e.preventDefault();
const canAuthenticated = await authentication(input);
if (canAuthenticated) {
history.push('/Account/mypage');
} else {
history.push('/Account/login');
}
};
useEffect(() => {
if (isSubmitSuccessful) {
reset({ username: '', password: '' });
}
}, [isSubmitSuccessful, reset]);
if (loading) return <p>Loading...</p>;
return (
<div>
{error ? <p>認証エラー</p> : null}
<form onSubmit={handleSubmit(onSubmit)}>
<input
name="username"
placeholder="username"
ref={register({ required: true })}
/>
{errors.username && <span>ユーザ名を入力してください。</span>}
<input
type="password"
name="password"
placeholder="password"
ref={register({ required: true })}
/>
{errors.password && <span>パスワードを入力してください。</span>}
<input type="submit" />
</form>
</div>
);
};
export default Login;
最後に
カスタムhooksを作成してログイン画面を作成してみました。
hooks内にロジックを収めることで、大分見通しの良いコードになったと思います。
まだまだ初歩的な事しかできていませんが、これからもReactの学習を進めていき共有していきたいと思います。
参考サイト
https://zenn.dev/maktub_bros/articles/4bcad345e5420a
https://www.apollographql.com/docs/react/get-started/
https://react-hook-form.com/jp/get-started