[11 | ログイン機能実装] << [ホーム] >> [13 | ユーザー登録]
「qrmenu_react/src」フォルダの中に「contexts」フォルダを新規作成します。
新規作成した「contexts」フォルダの中に「AuthContext.js」ファイルを新規作成します。
新規作成 【QRMenu/qrmenu_react/src/contexts/AuthContext.js】
import React, { createContext, useState } from 'react'; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [token, setToken] = useState(""); const value = { token, } return <AuthContext.Provider value={value}>{children}</AuthContext.Provider> }; export default AuthContext;
「qrmenu_react/src/pages」フォルダの中に「Places.js」ファイルを新規作成します。
新規作成 【QRMenu/qrmenu_react/src/pages/Places.js】
import React from 'react'; import MainLayout from '../layouts/MainLayout'; const Places = () => <MainLayout>飲食店</MainLayout> export default Places;
「qrmenu_react/src/router/App.js」ファイルに記述を追加します。
記述追加 【Desktop/QRMenu/qrmenu_react/src/router/App.js】
import { BrowserRouter, Switch, Route } from 'react-router-dom'; import { ToastContainer } from 'react-toastify'; import React from 'react'; import Home from '../pages/Home'; import Login from '../pages/Login'; import Places from '../pages/Places'; function App() { return ( <> <BrowserRouter> <Switch> <Route exact path='/'> <Home /> </Route> <Route exact path='/login'> <Login /> </Route> <Route exact path='/places'> <Places /> </Route> </Switch> </BrowserRouter> <ToastContainer /> </> ) } export default App;
ブラウザを確認します。
http://localhost:3000/places
プライベートルートを設定します。
「qrmenu_react/src/router」フォルダの中に「PrivateRoute.js」ファイルを新規作成します。
新規作成 【QRMenu/qrmenu_react/src/router/PrivateRoute.js】
import { Route, Redirect } from 'react-router-dom'; import React, { useContext } from 'react'; import AuthContext from '../contexts/AuthContext'; function PrivateRoute({ children, ...rest }) { const auth = useContext(AuthContext); return ( <Route {...rest} render={({ location }) => auth.token ? ( children ) : ( <Redirect to={{ pathname: '/login', state: { from: location }, }} /> ) } /> ) } export default PrivateRoute;
「qrmenu_react/src/router/App.js」ファイルに記述を追加します。
記述追加 【QRMenu/qrmenu_react/src/router/App.js】
import { BrowserRouter, Switch, Route } from 'react-router-dom'; import { ToastContainer } from 'react-toastify'; import React from 'react'; import { AuthProvider } from '../contexts/AuthContext'; import PrivateRoute from './PrivateRoute'; import Home from '../pages/Home'; import Login from '../pages/Login'; import Places from '../pages/Places'; function App() { return ( <AuthProvider> <BrowserRouter> <Switch> <Route exact path='/'> <Home /> </Route> <Route exact path='/login'> <Login /> </Route> <PrivateRoute exact path='/places'> <Places /> </PrivateRoute> </Switch> </BrowserRouter> <ToastContainer /> </AuthProvider> ) } export default App;
「qrmenu_react/src/layouts/MainLayout.js」ファイルを編集します。
記述編集 【QRMenu/qrmenu_react/src/layouts/MainLayout.js】
import { Navbar, Nav, Container } from 'react-bootstrap'; import { useHistory } from 'react-router-dom'; import React from 'react'; const MainLayout = ({ children }) => { const history = useHistory(); const onSignIn = () => { history.replace("/login"); } const goToPlaces =() => { history.push("/places"); } return ( <> <Navbar bg="dark" variant="dark" className="mb-4"> <Navbar.Brand href="/">QRオーダーシステム</Navbar.Brand> <Nav> <Nav.Link onClick={goToPlaces}>飲食店</Nav.Link> </Nav> <Nav className="flex-grow-1 justify-content-end"> <Nav.Link onClick={onSignIn}>ログイン</Nav.Link> </Nav> </Navbar> <Container>{children}</Container> </> ) } export default MainLayout;
「qrmenu_react/src/apis.js」ファイルの記述を変更します。
38行目〜41行目の記述を削除しています。
記述削除 【QRMenu/qrmenu_react/src/apis.js】38〜41行目
import { toast } from 'react-toastify'; export function signIn(username, password) { return fetch("/auth/token/login/", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username, password }), }) .then((response) => { console.log(response); //もし成功したら if (response.ok) { toast.success("ログイン成功"); return response.json(); } //失敗 return response.json().then((json) => { //JSONエラー if (response.status === 400) { toast.error("氏名もしくはパスワードに間違いがあります。"); const errors = Object.keys(json).map( (k) => `${(json[k].join(" "))}` ); throw new Error(errors.join(" ")); } throw new Error(JSON.stringify(json)); }) .catch((e) => { if (e.name === "SyntaxError") { throw new Error(response.statusText); } throw new Error(e); }) }) .catch((e) => { //全エラー toast(e.message, { type: "error" }); }) }
Tokenの受け渡しを実装します。
「qrmenu_react/src/contexts/AuthContext.js」ファイルを編集します。
記述編集 【QRMenu/qrmenu_react/src/contexts/AuthContext.js】
import React, { createContext, useState } from 'react'; import {signIn as signInApi} from '../apis'; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [token, setToken] = useState(localStorage.getItem("token")); const [loading, setLoading] = useState(false); const signIn = async (username, password, callback) => { setLoading(true); const response = await signInApi(username, password); console.log("response", response); if(response && response.auth_token) { localStorage.setItem("token", response.auth_token); setToken(response.auth_token); callback(); } setLoading(false); } const signOut = () => { localStorage.removeItem("token"); setToken(""); } const value = { token, loading, signIn, signOut, } return <AuthContext.Provider value={value}>{children}</AuthContext.Provider> }; export default AuthContext;
「qrmenu_react/src/pages/Login.js」ファイルを編集します。
記述編集 【QRMenu/qrmenu_react/src/pages/Login.js】
import { Button, Col, Form, Row, Card, Spinner } from "react-bootstrap"; import { React, useState, useEffect, useContext } from "react"; import { useHistory } from 'react-router-dom'; import { signIn } from '../apis'; import MainLayout from '../layouts/MainLayout'; import AuthContext from '../contexts/AuthContext'; const Login = () => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const history = useHistory(); const auth = useContext(AuthContext); useEffect(() => { if(auth.token) { history.replace('/places'); } }); const onClick = () => { auth.signIn(username, password, () => history.replace("/places")); }; return ( <MainLayout> <Row className="justify-content-center"> <Col lg={6} mg={8}> <Card> <Card.Body> <div className="form-title text-center"> <b>ログイン</b> </div> <Form.Group> <Form.Label>氏名</Form.Label> <Form.Control type="text" placeholder="氏名を入力" value={username} onChange={(e) => setUsername(e.target.value)} /> <Form.Label>パスワード</Form.Label> <Form.Control type="password" placeholder="パスワードを入力" value={password} onChange={(e) => setPassword(e.target.value)} /> </Form.Group> <Button variant="standard" block onClick={onClick} disabled={auth.loading}> { auth.loading ? ( <Spinner variant="standard" as="apan" animation="border" size="sm" role="status" aria-hidden="true" /> ) : ( "ログイン" ) } </Button> </Card.Body> </Card> </Col> </Row> </MainLayout> ); }; export default Login;
ログインに成功すると「places」ページにリダイレクトして開けるようになりました。
ログアウトできるように実装します。
「src/layouts/MainLayout.js」ファイルを編集します。
記述編集 【QRMenu/qrmenu_react/src/layouts/MainLayout.js】
import { Navbar, Nav, Container } from 'react-bootstrap'; import { useHistory } from 'react-router-dom'; import React, { useContext } from 'react'; import AuthContext from '../contexts/AuthContext'; const MainLayout = ({ children }) => { const history = useHistory(); const auth = useContext(AuthContext); const onSignIn = () => { history.replace("/login"); } const onSignOut = () => { auth.signOut(); history.push("/login"); } const goToPlaces =() => { history.push("/places"); } return ( <> <Navbar bg="dark" variant="dark" className="mb-4"> <Navbar.Brand href="/">QRオーダーシステム</Navbar.Brand> <Nav> <Nav.Link onClick={goToPlaces}>飲食店</Nav.Link> </Nav> <Nav className="flex-grow-1 justify-content-end"> {auth.token ? ( <Nav.Link onClick={onSignOut}>ログアウト</Nav.Link> ) : ( <Nav.Link onClick={onSignIn}>ログイン</Nav.Link> )} </Nav> </Navbar> <Container>{children}</Container> </> ) } export default MainLayout;
これでログアウトした時にログインページにリダイレクトできるようになりました。
↓↓クリックして頂けると励みになります。
[11 | ログイン機能実装] << [ホーム] >> [13 | ユーザー登録]