はじめに
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
に格納したトークンで問い合わせ、ログイン済みか判断します。
-
画面の準備
ログインページとルートページを準備します。
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;
import React from "react";
const Home = () => {
return <div>Home</div>;
};
export default Home;
ルートの準備
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
にアクセス
http://localhost:3000/
にアクセス
ページとルートの準備が出来ました。
react-apollo
の準備
client
を作成します。
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
にはバックエンドのエンドポイントを指定してください。
リクエストヘッダにtoken
をlocalStorage
から取得して、セットします。
今回はRails + API + GraphQL
のプロジェクトを5000
番ポートで起動していました。
react-apollo-hooks
を使う準備が整ったので早速ログイン機能から作って行きましょう。
ログイン機能
以下のようなMutation
をバックエンド側に用意しておきます。
mutation {
login(input: {loginid: "loginid", password: "password"}) {
user {
id
accessToken {
token
}
}
result
}
}
これにログインフォームに入力された値を埋め込みリクエストを送りたいと思います。
入力値をStateに格納
-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 || "")}
+/>
...
ログインボタンをログイン処理実行
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.result
がtrue
)するとルートページに移動し、失敗するとalert
を出す処理が出来ました。
ログインに成功した場合はレスポンスから取得したトークンをlocalStorage
にセットしています。
localStorage
にトークンをセットする処理が出来たので、既にログイン済みの場合、ルートページにリダイレクトする処理を作ります。
ログイン済みユーザのルートページへ自動リダイレクト処理
以下のようなログイン済みユーザを取得するQuery
をバックエンド側に用意しておきます。
{
loggedUser {
id
}
}
レスポンスにユーザ情報が含まれている場合は、ログイン済み、null
の場合は、未ログインと判断します。
...
-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
を画面左下のフォームにて設定する必要があります。