こんにちは、KOUKIです。Reactで画面開発を行なっています。
前回は、ログイン処理を実装しました。
今回は、ログイン認証とログアウトを実装します。
尚、「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
<目次>
前回
事前準備
フォルダ/ファイル
1 |
touch react-admin/src/components/Layout.tsx |
コンポーネントとProps
Reactには、UIを部品として独立させ、再利用性を向上させるコンポーネントと呼ばれる概念があります。
DashboardのナビゲーションやMenuなどをUserページから切り離して、コンポーネント化しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// components/Layout.tsx import Nav from '../components/Nav' import Menu from '../components/Menu' const Layout = (props: any) => { return ( <div> <Nav /> <div className="container-fluid"> <div className="row"> <Menu /> <main className="col-md-9 ms-sm-auto col-lg-10 px-md-4"> <div className="table-responsive"> {props.children} </div> </main> </div> </div> </div> ) } export default Layout |
コンポーネントを作る方法は、2つあります。関数コンポーネントとクラスコンポーネントです。
上記は、関数コンポーネントで実装しました。
Layoutの引数(props: any)には、Props(プロパティの意味)を指定しています。こうすると、Layoutで括ったHTML要素をこのpropsに渡すことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// pages/Users.tsx import Layout from '../components/Layout' const Users = () => { return ( <Layout> <table className="table table-striped table-sm"> <thead> <tr> <th scope="col">#</th> <th scope="col">Header</th> <th scope="col">Header</th> <th scope="col">Header</th> <th scope="col">Header</th> </tr> </thead> <tbody> <tr> <td>1,001</td> <td>random</td> <td>data</td> <td>placeholder</td> <td>text</td> </tr> </tbody> </table> </Layout> ) } export default Users |
上記では、Propsとしてtable要素を渡しています。それを、Layoutコンポーネント側で「{props.children}」を指定して取り出しています。
docker-compose upでコンテナを起動して、「http://localhost:3000」にアクセスしてみましょう。
コンポーネント化した後でも、Dashboardが表示されていたら成功です。
ログイン認証機能
前回、ログイン機能を実装しました。その時は触れなかったのですが、ログインが成功するとブラウザ上にCookie情報が登録されます。

この情報は、ユーザーがログインの有無を確認する認証機能として利用できます。
ログイン済みかチェックする
先ほど実装したLayout関数に、ログイン認証機能を実装してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// components/Layout.tsx import { useEffect, useState } from 'react' import { Redirect } from 'react-router-dom' import Nav from '../components/Menu' import Menu from '../components/Menu' import axios from 'axios' const Layout = (props: any) => { // baseURLはindex.tsxに設定してある const userURL = 'user' const [redirect, setRedirect] = useState(false) // useEffect // https://ja.reactjs.org/docs/hooks-reference.html#useeffect // Reactアプリの状態の変更を検知して、実行 useEffect(() => { ( async () => { try { // ユーザー情報の取得 const { data } = await axios.get(userURL) console.log({data}) } catch(e) { // 認証情報が取得できない == ログインしていない setRedirect(true) } } )() }, []) if (redirect) { // ログイン画面へリダイレクト return <Redirect to={'/login'} /> } return ( ... } export default Layout |
useEffectを使って、ページの表示後にAPI側へユーザー情報を取得する処理を実装しました。
前回、index.tsxに「axios.defaults.withCredentials = true」を設定したので、axios通信にはCookie情報を必須としました。
もし情報の取得に失敗した場合、未認証扱いとしてログイン画面にリダイレクトします。
Cookieは、ブラウザから「右クリック->Deleteボタン」で消すことができるので、削除後に「http://localhost:3000/」にアクセスして、ログイン画面に戻るか確認してください。


ログアウト
ユーザー認証ができたので、今度はログアウト処理を実装しましょう。
これが実装できれば、わざわざブラウザからCookieを手動で削除しなくて済みますからね^^
Propsインターフェースの定義
ログアウトは、Navコンポーネントに追加します。
その際に、親ページのLayoutからユーザー情報をPropsとして渡す必要があります。
本アプリではTypeScriptを使用しており、Propsの型が必要になるので、models/user.tsに実装しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
// models/user.ts export class User { ... } export interface UserProps { id: number first_name: string last_name: string email: string } |
この型は、Navコンポーネントにパラメータとして指定します。
1 2 3 4 5 6 7 |
// src/components/Nav.tsx import { UserType as User} from '../models/user' // propsを追加 const Nav = (props: { user: User }) => { .... } |
続いて、Layout.tsxからユーザー情報をNavコンポーネントに渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// components/Layout.tsx ... const Layout = (props: any) => { const userURL = 'user' const [redirect, setRedirect] = useState(false) // User情報のState const [user, setUser] = useState<UserProps | null>(null) useEffect(() => { ( async () => { try { const { data } = await axios.get(userURL) // User情報をセット setUser(data) } catch(e) { setRedirect(true) } } )() }, []) if (redirect) {...} return ( <div> <Nav user={user}/> // user情報を渡す ... </div> ) } |
追加した処理は、以下です。
- useStateを使って、Reactアプリにユーザー情報を状態として持たせる
- axiosのPOSTリクエスト後に、ユーザー情報をセットする
- Navコンポーネントに、ユーザー情報をPropsとして渡す
ルーティング処理
次に、Nav.tsxのreturnの中身を書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// src/components/Nav.tsx import { Link } from 'react-router-dom' import { UserProps } from '../models/user' // propsを追加 const Nav = (props: { user: UserProps | null }) => { const logout = () => { } return ( <header className="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow"> <a className="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">Company name</a> <div className="navbar-nav"> <div className="nav-item text-nowrap"> <Link to={'/profile'} className="nav-link px-3" > {props.user?.first_name} {props.user?.last_name} </Link> <Link to={'/login'} className="nav-link px-3" onClick={logout} > Sign out </Link> </div> </div> </header> ) } export default Nav |
ここでは、ReactのLinkを使って、ルーティング処理を実装しました。
ログアウトの実装
最後に、ログアウトを実装します。
1 2 3 4 5 6 7 |
// src/components/Nav.tsx import axios from 'axios' // propsを追加 const Nav = (props: { user: UserProps | null }) => { const logout = async () => { await axios.post('logout') } } |
index.tsxに、ベースとなるURLを設定しているので、’logout’を指定するだけで、API側へリクエストを送信できます。
1 2 |
// src/index.tsx axios.defaults.baseURL = 'http://localhost:8000/api/admin/ |
検証
検証してみましょう。
次回
次回は、ユーザー一覧の実装をしましょう。
Reactまとめ
参考書籍
ソースコード
ここまで実装したソースコードを下記に記載します。
Layout.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
// components/Layout.tsx import { useEffect, useState } from 'react' import { Redirect } from 'react-router-dom' import { UserProps } from '../models/user' import Nav from '../components/Nav' import Menu from '../components/Menu' import axios from 'axios' const Layout = (props: any) => { const userURL = 'user' const [redirect, setRedirect] = useState(false) // User情報のState const [user, setUser] = useState<UserProps | null>(null) useEffect(() => { ( async () => { try { const { data } = await axios.get(userURL) // User情報をセット setUser(data) } catch(e) { setRedirect(true) } } )() }, []) if (redirect) { // ログイン画面へリダイレクト return <Redirect to={'/login'} /> } return ( <div> <Nav user={user}/> <div className="container-fluid"> <div className="row"> <Menu /> <main className="col-md-9 ms-sm-auto col-lg-10 px-md-4"> <div className="table-responsive"> {props.children} </div> </main> </div> </div> </div> ) } export default Layout |
Nav.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// src/components/Nav.tsx import { Link } from 'react-router-dom' import { UserProps } from '../models/user' import axios from 'axios' // propsを追加 const Nav = (props: { user: UserProps | null }) => { const logout = async () => { await axios.post('logout') } return ( <header className="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow"> <a className="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">Company name</a> <div className="navbar-nav"> <div className="nav-item text-nowrap"> <Link to={'/profile'} className="nav-link px-3" > {props.user?.first_name} {props.user?.last_name} </Link> <Link to={'/login'} className="nav-link px-3" onClick={logout} > Sign out </Link> </div> </div> </header> ) } export default Nav |
user.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// models/user.ts export class User { first_name: string last_name: string email: string password: string password_confirm: string constructor( first_name: string, last_name: string, email: string, password: string, password_confirm: string) { this.first_name = first_name this.last_name = last_name this.email = email this.password = password this.password_confirm = password_confirm } } export interface UserProps { id: number first_name: string last_name: string email: string } |
Menu.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// src/components/Menu.tsx const Menu = () => { return ( <nav id="sidebarMenu" className="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse"> <div className="position-sticky pt-3"> <ul className="nav flex-column"> <li className="nav-item"> <a className="nav-link active" aria-current="page" href="#"> <span data-feather="home"></span> Dashboard </a> </li> </ul> </div> </nav> ) } export default Menu |
User.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// pages/Users.tsx import Layout from '../components/Layout' const Users = () => { return ( <Layout> <table className="table table-striped table-sm"> <thead> <tr> <th scope="col">#</th> <th scope="col">Header</th> <th scope="col">Header</th> <th scope="col">Header</th> <th scope="col">Header</th> </tr> </thead> <tbody> <tr> <td>1,001</td> <td>random</td> <td>data</td> <td>placeholder</td> <td>text</td> </tr> </tbody> </table> </Layout> ) } export default Users |
コメントを残す
コメントを投稿するにはログインしてください。