こんにちは、KOUKIです。
今回は、ヘッダーコンポーネントの実装をします。
尚、「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
前回
前回は、ナビゲーションの実装を行いました。
事前準備
ファイル
1 |
touch react-ambassador/src/pages/Profile.tsx |
ヘッダーコンポーネントの変更
まずは、以前作成したHeader.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 |
// components/Header.tsx import { useState } from 'react' import { Link } from 'react-router-dom' const Header = () => { const [title, setTitle] = useState('Welcome') const [description, setDescription] = useState('Share links to earn money') return ( <section className="py-5 text-center container"> <div className="row py-lg-5"> <div className="col-lg-6 col-md-8 mx-auto"> <h1 className="fw-light">{title}</h1> <p className="lead text-muted">{description}</p> <p> <Link to={'/login'} className="btn btn-primary my-2">Login</Link> <Link to={'/register'} className="btn btn-secondary my-2">Register</Link> </p> </div> </div> </section> ) } export default Header |

Redux Storeへ接続
ヘッダーコンポーネントもRedux Storeへ接続し、Stateの値を取得できるようにしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// components/Header.tsx ... import { User } from '../models/user' import { connect } from 'react-redux' // propsを渡せるようにする const Header = (props: {user: User}) => { ... } // Redux Storeへ接続 export default connect( (state: {user: User}) => ({ user: state.user }) )(Header) |
表示の切り替え
ナビゲーションの時と同様に、ログイン済みの場合は、ユーザー情報を表示するようにします。
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/Header.tsx import { useEffect, useState } from 'react' ... const Header = (props: {user: User}) => { const [title, setTitle] = useState('Welcome') const [description, setDescription] = useState('Share links to earn money') useEffect(() => { if (props.user?.id) { setTitle(`$${props.user.revenue}`) setDescription('You have earned this far') } else { setTitle('Welcome') setDescription('Share links to earn money') } }, [props.user]) let buttons; if (!props.user?.id) { buttons = ( <p> <Link to={'/login'} className="btn btn-primary my-2">Login</Link> <Link to={'/register'} className="btn btn-secondary my-2">Register</Link> </p> ) } return ( <section className="py-5 text-center container"> <div className="row py-lg-5"> <div className="col-lg-6 col-md-8 mx-auto"> <h1 className="fw-light">{title}</h1> <p className="lead text-muted">{description}</p> {buttons} </div> </div> </section> ) } ... |
Userモデルに、revenueを追加します。
1 2 3 4 5 6 |
// models/user.ts export class User { ... revenue?: number // 追加 } |
また、現状ログアウトするとログインページに遷移してしまうので、前回作成したナビゲーションからリダイレクト処理を削除し、ユーザー情報を設定する処理を追加します。
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 |
// components/Nav.tsx import { Dispatch } from 'react' import { connect } from 'react-redux' import { User } from '../models/user' import { Link } from 'react-router-dom' import axios from 'axios' import { setUserAction } from '../redux/actions/setUserAction' // propsをanyへ変更 const Nav = (props: any) => { // const [redirect, setRedirect] = useState(false) const logout = async () => { await axios.post('logout') // setRedirect(true) props.setUser(null) // ユーザをセット } // if (redirect) { // return <Redirect to={'/login'} /> // } let menu if (props.user?.id) { ... ) } else { ... } return ( ... ) } export default connect( (state: {user: User}) => ({ user: state.user }), // dispatchを追加 (dispatch: Dispatch<any>) => ({ setUser: (user: User) => dispatch(setUserAction(user)) }) )(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 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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
// pages/Profile.tsx import { Dispatch, SyntheticEvent, useEffect, useState } from 'react' import { connect } from 'react-redux' import { User } from '../models/user' import { setUserAction } from '../redux/actions/setUserAction' import Layout from '../components/Layout' import axios from 'axios' // Props追加 const Profile = (props: any) => { const [first_name, setFirstName] = useState("") const [last_name, setLastName] = useState("") const [email, setEmail] = useState("") const [password, setPassword] = useState("") const [password_confirm, setPasswordConfirm] = useState("") const userInfo = 'users/info' const userPassword = 'users/password' // propsからデータを設定するのでAPIからデータを取得する必要はなくなる useEffect(() => { console.log(props.user) setFirstName(props.user.first_name) setLastName(props.user.last_name) setEmail(props.user.email) }, [props.user]) // propsを渡す const infoSubmit = async(e: SyntheticEvent) => { e.preventDefault() const { data } = await axios.put(userInfo, { first_name, last_name, email }) // dispatchを実行 props.setUser(data) } const passwordSubmit = async(e: SyntheticEvent) => { e.preventDefault() await axios.put(userPassword, { password, password_confirm }) } return ( <Layout> <h3>Account Information</h3> <form onSubmit={infoSubmit}> <div className="mb-3"> <label>First Name</label> <input className="form-control" defaultValue={first_name} onChange={e => setFirstName(e.target.value)} /> </div> <div className="mb-3"> <label>Last Name</label> <input className="form-control" defaultValue={last_name} onChange={e => setLastName(e.target.value)} /> </div> <div className="mb-3"> <label>Email</label> <input className="form-control" defaultValue={email} onChange={e => setEmail(e.target.value)} /> </div> <button className="btn btn-outline-secondary" type="submit"> Submit </button> </form> <h3 className="mt-4">Change Password</h3> <form onSubmit={passwordSubmit}> <div className="mb-3"> <label>Password</label> <input className="form-control" onChange={e => setPassword(e.target.value)} /> </div> <div className="mb-3"> <label>Password Confirm</label> <input className="form-control" onChange={e => setPasswordConfirm(e.target.value)} /> </div> <button className="btn btn-outline-secondary" type="submit"> Submit </button> </form> </Layout> ) } export default connect( (state: {user: User}) => ({ user: state.user }), // dispatchを追加 (dispatch: Dispatch<any>) => ({ setUser: (user: User) => dispatch(setUserAction(user)) }) )(Profile) |
内容は、Admin画面で実装した時のものとほぼ一緒です。
このページへのルートを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// App.tsx ... import Profile from './pages/Profile' function App() { return ( <BrowserRouter> ... <Route path={'/profile'} component={Profile} /> </BrowserRouter> ); } export default App; |
バグの修正
ページを更新後、「$undefined」になりました。これの修正を行いましょう。

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 |
// components/Layout.tsx ... import { useLocation } from 'react-router' ... const Layout = (props: any) => { ... const location = useLocation() useEffect(() => { ... }, []) // Header表示/非表示の判定 let header if (location.pathname === '/' || location.pathname === '/backend') { header = <Header /> } return ( <div> <Nav /> <main> {header} // 追加 ... ) } .... |
URLがルートかbackend以外の場合は、Headerコンポーネントを非表示にしました。

次回
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 53 54 55 56 57 58 59 60 |
// components/Layout.tsx import { Dispatch, useEffect } from 'react' import { connect } from 'react-redux' import { User } from '../models/user' import { setUserAction } from '../redux/actions/setUserAction' import { useLocation } from 'react-router' import Nav from './Nav' import Header from './Header' import axios from 'axios' const Layout = (props: any) => { const userURL = 'user' const location = useLocation() useEffect(() => { ( async () => { try { const { data } = await axios.get(userURL) props.setUser(data) } catch(e) { console.log(e) } } )() }, []) let header if (location.pathname === '/' || location.pathname === '/backend') { header = <Header /> } return ( <div> <Nav /> <main> {header} <div className="album py-5 bg-light"> <div className="container"> {props.children} </div> </div> </main> </div> ) } // State const mapStateToProps = (state: {user: User}) => ({ user: state.user }) // Dispatch const mapDispatchToProps = (dispatch: Dispatch<any>) => ({ // setUser(Action)->dispatch setUser: (user: User) => dispatch(setUserAction(user)) }) // LayoutコンポーネントをRedux Storeに登録 export default connect(mapStateToProps, mapDispatchToProps)(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 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 |
// components/Nav.tsx import { Dispatch } from 'react' import { connect } from 'react-redux' import { User } from '../models/user' import { Link } from 'react-router-dom' import axios from 'axios' import { setUserAction } from '../redux/actions/setUserAction' const Nav = (props: any) => { const logout = async () => { await axios.post('logout') props.setUser(null) } let menu if (props.user?.id) { menu = ( <div className="col-md-3 text-end"> <Link to={'/profile'} href="#" className="btn btn-primary"> {props.user.first_name} {props.user.last_name} </Link> <a href="#" className="btn btn-outline-primary me-2" onClick={logout} > logout </a> </div> ) } else { menu = ( <div className="col-md-3 text-end"> <Link to={'/login'} className="btn btn-outline-primary me-2">Login</Link> <Link to={'/register'} className="btn btn-primary">Sign-up</Link> </div> ) } return ( <div className="container"> <header className="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom"> <ul className="nav col-12 col-md-auto mb-2 justify-content-center mb-md-0"> <li><a href="#" className="nav-link px-2 link-secondary">Frontend</a></li> <li><a href="#" className="nav-link px-2 link-dark">Backend</a></li> </ul> {menu} </header> </div> ) } export default connect( (state: {user: User}) => ({ user: state.user }), (dispatch: Dispatch<any>) => ({ setUser: (user: User) => dispatch(setUserAction(user)) }) )(Nav) |
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// App.tsx import { BrowserRouter, Route } from 'react-router-dom' import ProductsFrontend from './pages/ProductsFrontend' import Login from './pages/Login' import Register from './pages/Register' import Profile from './pages/Profile' import './App.css'; function App() { return ( <BrowserRouter> <Route path={'/'} exact component={ProductsFrontend} /> <Route path={'/login'} component={Login} /> <Route path={'/register'} component={Register} /> <Route path={'/profile'} component={Profile} /> </BrowserRouter> ); } export default App; |
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 53 54 55 56 57 58 59 60 |
// components/Layout.tsx import { Dispatch, useEffect } from 'react' import { connect } from 'react-redux' import { User } from '../models/user' import { setUserAction } from '../redux/actions/setUserAction' import { useLocation } from 'react-router' import Nav from './Nav' import Header from './Header' import axios from 'axios' const Layout = (props: any) => { const userURL = 'user' const location = useLocation() useEffect(() => { ( async () => { try { const { data } = await axios.get(userURL) props.setUser(data) } catch(e) { console.log(e) } } )() }, []) let header if (location.pathname === '/' || location.pathname === '/backend') { header = <Header /> } return ( <div> <Nav /> <main> {header} <div className="album py-5 bg-light"> <div className="container"> {props.children} </div> </div> </main> </div> ) } // State const mapStateToProps = (state: {user: User}) => ({ user: state.user }) // Dispatch const mapDispatchToProps = (dispatch: Dispatch<any>) => ({ // setUser(Action)->dispatch setUser: (user: User) => dispatch(setUserAction(user)) }) // LayoutコンポーネントをRedux Storeに登録 export default connect(mapStateToProps, mapDispatchToProps)(Layout) |
user.ts
1 2 3 4 5 6 7 8 9 10 11 |
// models/user.ts export class User { id?: number first_name!: string last_name!: string email!: string password?: string password_confirm?: string revenue?: number } |
Header.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 |
// components/Header.tsx import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { User } from '../models/user' import { connect } from 'react-redux' const Header = (props: {user: User}) => { const [title, setTitle] = useState('Welcome') const [description, setDescription] = useState('Share links to earn money') useEffect(() => { if (props.user?.id) { setTitle(`$${props.user.revenue}`) setDescription('You have earned this far') } else { setTitle('Welcome') setDescription('Share links to earn money') } }, [props.user]) let buttons; if (!props.user?.id) { buttons = ( <p> <Link to={'/login'} className="btn btn-primary my-2">Login</Link> <Link to={'/register'} className="btn btn-secondary my-2">Register</Link> </p> ) } return ( <section className="py-5 text-center container"> <div className="row py-lg-5"> <div className="col-lg-6 col-md-8 mx-auto"> <h1 className="fw-light">{title}</h1> <p className="lead text-muted">{description}</p> {buttons} </div> </div> </section> ) } export default connect( (state: {user: User}) => ({ user: state.user }) )(Header) |
コメントを残す
コメントを投稿するにはログインしてください。