こんにちは、KOUKIです。
GolangとReactでMovie Appの開発をしています。
今回は、ReactでLoginページを作成し、前回作成したLogin APIへリクエストを送信する処理を実装します。
尚、Udemyの「Working with React and Go (Golang)」を参考にしているので、よかったら受講してみてください。
<目次>
前回
JWTを使ってSign-in処理を実装しました。
事前準備
フォルダ/ファイル
1 2 |
touch go-movies/src/components/Login.tsx touch go-movies/src/models/tokens.ts |
ログイン処理
前回実装したJWTを使ったログイン処理の実装を行います。
ログイン/ログアウトボタンの設置
まずは、UI部分から攻めていきます。
画面の右上に「ログイン/ログアウト」ボタン(リンク)を設置しましょう。
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 |
// src/App.tsx import { useState } from 'react' ... export default function App() { const [jwt, setJWT] = useState("") const handleJWTChange = (jwt: string) => { setJWT(jwt) } const logout = () => { setJWT("") } let loginLink if (jwt === "") { loginLink = <Link to="/login">Login</Link> } else { loginLink = <Link to="/logout" onClick={logout}>Logout</Link> } return ( <Router> <div className="container"> <div className="row"> <div className="col mt-3"> <h1 className="mt-3"> Go Watch a Movie! </h1> </div> <div className="col mt-3 text-end"> {loginLink} </div> <hr className="mb-3" /> </div> .... </div> </Router> ) } |
useStateを使って、JWTの値をStateに保存するようにしました。ログイン/ログアウトボタンの表示切り替えはこのJWTに値が入っているか否かの判定結果で行います。
「docker-compose up」でコンテナを起動し、「http://localhost:3000」にアクセスしてください。

ページの右上に「Login」が表示されましたね。
ページ制御
「Add Movie」および「Manage Catalog」は、ログイン済みでないと表示されないようにします。
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 |
// src/App.tsx ... export default function App() { ... return ( <Router> ... <div className="row"> ... </li> { jwt !== "" && <Fragment> <li className="list-group-item"> <Link to="/admin/movie/0">Add Movie</Link> </li> <li className="list-group-item"> <Link to="/admin">Manage Catalog</Link> </li> </Fragment> } </ul> </nav> </div> ... } |

ログインページの実装
続いて、ログインページを実装します。実装する内容は今まで実装した他のコンポーネントとほぼ同じなので、さらっと書きます。
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 53 54 55 56 57 58 59 |
// components/Login.tsx import { useState, Fragment, SyntheticEvent } from 'react' import { AlertType } from '../models/ui-components' import Alert from './ui-components/Alert' import Input from './form-components/Input' const Login = () => { const [email, setEmail] = useState("") const [password, setPassword] = useState("") const [error, setError] = useState("") const [errors, setErrors] = useState<string[]>([]) const [alert, setAlert] = useState<AlertType>({type: "d-none", message: ""}) const submit = async (e: SyntheticEvent) => { e.preventDefault() } const hasError = (key: string) => { return errors.indexOf(key) !== -1 } return ( <Fragment> <h2>Login</h2> <hr /> <Alert alertType={alert.type} alertMessage={alert.message} /> <form className="pt-3" onSubmit={submit}> <Input title={"Email"} type={'email'} name={'email'} handleChange={setEmail} className={hasError("email") ? "is-invalid" : ""} errorDiv={hasError("email") ? "text-danger" : "d-none"} errorMsg={"Please enter a valid email address"} /> <Input title={"Password"} type={'password'} name={'password'} handleChange={setPassword} className={hasError("password") ? "is-invalid" : ""} errorDiv={hasError("password") ? "text-danger" : "d-none"} errorMsg={"Please enter a password"} /> <hr /> <button className="btn btn-primary">Login</button> </form> </Fragment> ) } export default Login |
このコンポーネントをApp.tsから読み込みます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// src/App.tsx ... import Login from './components/Login' export default function App() { ... return ( <Router> ... <div className="col-md-10"> <Switch> ... <Route exact path="/login" component={(props: any) => <Login {...props} handleJWTChange={handleJWTChange} />} /> ... </Switch> </div> </Router> ) } |
画面を表示します。

バリデーションの設定
Email/Passwordを未設定の状態で「Login」ボタンを押下したら、バリデーションエラーにします。
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 |
// components/Login.tsx ... const Login = () => { ... const submit = async (e: SyntheticEvent) => { e.preventDefault() // エラー初期化 setErrors([]) let submitErrors: string[] = [] if (email === "") { submitErrors.push("email") } if (password === "") { submitErrors.push("password") } if (submitErrors.length > 0) { setErrors(submitErrors) return } } ... } |

Payloadの作成
ログイン情報をAPIへ送信するために、Credentialsモデルを定義します。
1 2 3 4 5 |
// models/tokens.ts export interface Credentials { username: string password: string } |
作成したモデルをLoginコンポーネントから読み込んで、Payloadを完成させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// components/Login.tsx import { Credentials } from '../models/tokens' ... const Login = () => { ... const submit = async (e: SyntheticEvent) => { ... const payload: Credentials = { username: email, password: password } } } |
AxiosでPost通信
次に、AxiosでAPIへPost通信をします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// components/Login.tsx ... import axios from 'axios' const Login = () => { ... const submit = async (e: SyntheticEvent) => { ... await axios.post("signin", JSON.stringify(payload)) .then((response) => { console.log(response.data) }) .catch((err) => { setError(err.response.data.error.message) setAlert({ type: "alert-danger", message: err.response.data.error.message}) }) } ... } |
検証
ログイン確認をしましょう。
ログイン情報は、「cmd/api/tokens.go」ファイルに直書きしています。
- Email: me@here.com
- Password: password

responseが返ってきているので、成功ですね。
ログイン失敗時の挙動も見てみましょう。
- Email: me@here.com
- Password: pas

次回
記事まとめ
参考書籍
ソースコード
Login.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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
// components/Login.tsx import { useEffect, useState, Fragment, SyntheticEvent } from 'react' import { AlertType } from '../models/ui-components' import { Credentials } from '../models/tokens' import Alert from './ui-components/Alert' import Input from './form-components/Input' import axios from 'axios' const Login = () => { const [email, setEmail] = useState("") const [password, setPassword] = useState("") const [error, setError] = useState("") const [errors, setErrors] = useState<string[]>([]) const [alert, setAlert] = useState<AlertType>({type: "d-none", message: ""}) const submit = async (e: SyntheticEvent) => { e.preventDefault() // エラー初期化 setErrors([]) let submitErrors: string[] = [] if (email === "") { submitErrors.push("email") } if (password === "") { submitErrors.push("password") } if (submitErrors.length > 0) { setErrors(submitErrors) return } const payload: Credentials = { username: email, password: password } await axios.post("signin", JSON.stringify(payload)) .then((response) => { console.log(response.data) }) .catch((err) => { setError(err.response.data.error.message) setAlert({ type: "alert-danger", message: err.response.data.error.message}) }) } const hasError = (key: string) => { return errors.indexOf(key) !== -1 } return ( <Fragment> <h2>Login</h2> <hr /> <Alert alertType={alert.type} alertMessage={alert.message} /> <form className="pt-3" onSubmit={submit}> <Input title={"Email"} type={'email'} name={'email'} handleChange={setEmail} className={hasError("email") ? "is-invalid" : ""} errorDiv={hasError("email") ? "text-danger" : "d-none"} errorMsg={"Please enter a valid email address"} /> <Input title={"Password"} type={'password'} name={'password'} handleChange={setPassword} className={hasError("password") ? "is-invalid" : ""} errorDiv={hasError("password") ? "text-danger" : "d-none"} errorMsg={"Please enter a password"} /> <hr /> <button className="btn btn-primary">Login</button> </form> </Fragment> ) } export default Login |
tokens.ts
1 2 3 4 5 6 |
// models/tokens.ts export interface Credentials { username: string password: string } |
コメントを残す
コメントを投稿するにはログインしてください。