14
20

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

React Apollo Hooks を使ってログイン機能を作った

Last updated at Posted at 2019-07-16

はじめに

react-apollo-hooksが便利だったのでSPAなログイン機能を作りながら使い方を紹介します。

実際に作成したプロジェクトはGithubにあげてます。

今回もプロジェクトの雛形は create-react-app で作成します。

TypeScriptで作ってみたのでコマンドは以下です。

$ create-react-app sample-login-with-react-apollo-hooks --typescript

※バックエンド(APIサーバ)について紹介は含んでいません。

バージョン

パッケージ バージョン
typescript 3.5.3
react 16.9.0
react-apollo 3.0.1
react-apollo-hooks 0.5.0
graphql 14.4.2
graphql-tag 2.10.1
react-router-dom 5.0.1

今回作成する機能

  • ログインに成功するとルートページにリダイレクト
    • バックエンドから受け取ったトークンは、localStorageに格納します。
  • ログインに失敗するとエラーを表示
  • 既にログイン済みの場合、ルートページにリダイレクト
    • localStorageに格納したトークンで問い合わせ、ログイン済みか判断します。

画面の準備

ログインページとルートページを準備します。

src/Login/index.tsx
import React from "react";

const Login = () => {
  return (
    <div>
      <div>
        <label>
          ID
          <br />
          <input type="text" />
        </label>
      </div>
      <div>
        <label>
          PW
          <br />
          <input type="password" />
        </label>
      </div>
      <div>
        <input type="submit" value="ログイン" />
      </div>
    </div>
  );
};

export default Login;
src/Home/index.tsx
import React from "react";

const Home = () => {
  return <div>Home</div>;
};

export default Home;

ルートの準備

src/index.tsx
import React from "react";
import { Route, Switch } from "react-router-dom";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import * as serviceWorker from "./serviceWorker";
import Home from "./Home";
import Login from "./Login";

ReactDOM.render(
  <BrowserRouter>
    <Switch>
      <Route exact path="/" component={Home} />
      <Route path="/login" component={Login} />
    </Switch>
  </BrowserRouter>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

http://localhost:3000/loginにアクセス

スクリーンショット 2019-07-14 22.39.48.png

http://localhost:3000/にアクセス

スクリーンショット 2019-07-14 22.40.28.png

ページとルートの準備が出来ました。

react-apolloの準備

clientを作成します。

src/index.tsx
 import React from "react";
 import ReactDOM from "react-dom";
 import { BrowserRouter } from "react-router-dom";
 import * as serviceWorker from "./serviceWorker";
+import { ApolloProvider } from "react-apollo-hooks";
+import { InMemoryCache } from "apollo-cache-inmemory";
+import { ApolloClient } from "apollo-client";
+import { setContext } from "apollo-link-context";
+import { createHttpLink } from "apollo-link-http";
+import { Route, Switch } from "react-router-dom";
 import Home from "./Home";
 import Login from "./Login";

+const uri = "http://localhost:5000/graphql";
+const context = setContext((_, { headers }) => {
+  return {
+    headers: { ...headers, token: localStorage.getItem("token") }
+  };
+});
+const link = context.concat(createHttpLink({ uri }));
+const cache = new InMemoryCache();
+const client = new ApolloClient({ cache, link });

 ReactDOM.render(
   <BrowserRouter>
+    <ApolloProvider client={client}>
       <Switch>
         <Route exact path="/" component={Home} />
         <Route path="/login" component={Login} />
       </Switch>
+    </ApolloProvider>
   </BrowserRouter>,
   document.getElementById("root")
 );

 // If you want your app to work offline and load faster, you can change
 // unregister() to register() below. Note this comes with some pitfalls.
 // Learn more about service workers: https://bit.ly/CRA-PWA
 serviceWorker.unregister();

uriにはバックエンドのエンドポイントを指定してください。
リクエストヘッダにtokenlocalStorageから取得して、セットします。
今回はRails + API + GraphQLのプロジェクトを5000番ポートで起動していました。

react-apollo-hooksを使う準備が整ったので早速ログイン機能から作って行きましょう。

ログイン機能

以下のようなMutationをバックエンド側に用意しておきます。

mutation {
  login(input: {loginid: "loginid", password: "password"}) {
    user {
      id
      accessToken {
        token
      }
    }
    result
  }
}

これにログインフォームに入力された値を埋め込みリクエストを送りたいと思います。

入力値をStateに格納

src/Login/index.tsx
-import React from "react";
+import React, { useState } from "react";

...
 const Login = () => {
+  const [loginid, setLoginid] = useState("");
+  const [password, setPassword] = useState("");

...

-<input type="text" />
+<input
+  type="text"
+  value={loginid}
+  onChange={e => setLoginid(e.target.value || "")}
+/>

...

-<input type="text" />
+<input
+  type="password"
+  value={password}
+  onChange={e => setPassword(e.target.value || "")}
+/>

...

ログインボタンをログイン処理実行

src/Login/index.tsx
 import React, { useState } from "react";
+import { useMutation } from "react-apollo-hooks";
+import { withRouter } from "react-router";
+import { History } from "history";
+import gql from "graphql-tag";

+interface IProps {
+  history: History;
+}

+const Login = ({ history }: IProps) => {
...
+  const loginMutation = gql`
+    mutation login($loginid: String!, $password: String!) {
+      login(input: { loginid: $loginid, password: $password }) {
+        user {
+          accessToken {
+            token
+          }
+        }
+        result
+      }
+    }
+  `;
+
+  const [login] = useMutation(loginMutation, {
+    update: (_proxy, response) => {
+      if (response.data.login.result) {
+        localStorage.setItem(
+          "token",
+          response.data.login.user.accessToken.token
+        );
+        history.push("/");
+      } else {
+        alert("ログイン情報が不正です。");
+        setLoginid("");
+        setPassword("");
+      }
+    },
+    variables: { loginid, password }
+  });

   return (
...
-<input type="submit" value="ログイン" />
+<input type="submit" value="ログイン" onClick={() => login()} />
...

-export default Login;
+export default withRouter(Login);

ログインに成功(response.data.login.resulttrue)するとルートページに移動し、失敗するとalertを出す処理が出来ました。

ログインに成功した場合はレスポンスから取得したトークンをlocalStorageにセットしています。

localStorageにトークンをセットする処理が出来たので、既にログイン済みの場合、ルートページにリダイレクトする処理を作ります。

ログイン済みユーザのルートページへ自動リダイレクト処理

以下のようなログイン済みユーザを取得するQueryをバックエンド側に用意しておきます。

{
  loggedUser {
    id
  }
}

レスポンスにユーザ情報が含まれている場合は、ログイン済み、nullの場合は、未ログインと判断します。

src/Login/index.tsx
...
-import { withRouter } from "react-router";
+import { Redirect, withRouter } from "react-router";
...
-import { useMutation } from "react-apollo-hooks";
+import { useQuery, useMutation } from "react-apollo-hooks";
...

+  const loggedUserQuery = gql`
+    {
+      loggedUser {
+        id
+      }
+    }
+  `;

   const loginMutation = gql`
...
  const [login] = useMutation(loginMutation, {
...

+  const { loading, data } = useQuery(loggedUserQuery);

+  if (loading) {
+    return <div>Now Loading...</div>;
+  } else if (data.loggedUser) {
+    return <Redirect to="/" />;
+  }

   const login = () => {
...

ログインしているか問い合わせ中はローディングメッセージを表示し、ログインしていれば、ルートページにリダイレクト、そうでなければ、ログインページを表示することが出来るようになりました。

最後に

/graphiqlでクエリを試す際、フロント側で定義しているクエリをコピペするだけで実行出来るので、嬉しいですね。
※引数がある場合は、variablesを画面左下のフォームにて設定する必要があります。

参考文献

14
20
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
14
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?