2
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 3 years have passed since last update.

【React】カスタムhooksを作成してGraphQL JWT認証のログイン画面を作成する

Posted at

概要

前回作成した【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/に接続するクライアントを作成します。

index.tsx
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・・・エラーの状態を示すオブジェクト
useAuthentication.ts
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...が表示されるようになっています。

Login.tsx
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

2
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
2
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?