こんにちは、KOUKIです。
Reactで画面開発をハンズオン形式で記事にしています。
今回は、Productページの実装をします。
尚、「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
前回
事前準備
ファイル
1 2 |
touch react-ambassador/src/pages/ProductsBackend.tsx touch react-ambassador/src/pages/Products.tsx |
ProductsBackend
Productを表示するページを作成します。
ページの作成
以前作成した、ProductsFrontendページを元に実装します。
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 |
// pages/ProductsBackend.tsx import Layout from '../components/Layout' const ProductsBackend = () => { return ( <Layout> <div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3"> <div className="col"> <div className="card shadow-sm"> <svg className="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title> <rect width="100%" height="100%" fill="#55595c"/> <text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text> </svg> <div className="card-body"> <p className="card-text"> This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer. </p> <div className="d-flex justify-content-between align-items-center"> <div className="btn-group"> <button type="button" className="btn btn-sm btn-outline-secondary">View</button> <button type="button" className="btn btn-sm btn-outline-secondary">Edit</button> </div> <small className="text-muted">9 mins</small> </div> </div> </div> </div> </div> </Layout> ) } export default ProductsBackend |
ナビゲーションの変更
Frontend/Backendナビゲーションのリンクを変更します。
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/Nav.tsx import { Link, NavLink } from 'react-router-dom' ... const Nav = (props: any) => { ... 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> <NavLink to={'/'} activeClassName='link-dark' exact className="nav-link px-2 link-secondary"> Frontend </NavLink> </li> <li> <NavLink to={'/backend'} activeClassName='link-dark' className="nav-link px-2 link-secondary"> Backend </NavLink> </li> </ul> {menu} </header> </div> ) } ... |
ルートの追加
ProductsBackendへのルートを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// App.tsx ... import ProductsBackend from './pages/ProductsBackend' ... function App() { return ( <BrowserRouter> ... <Route path={'/backend'} exact component={ProductsBackend} /> ... </BrowserRouter> ); } export default App; |
検証
ブラウザから「http://localhost:4000/backend」へアクセスしてみましょう。

OKですね。
Products
Product一覧を表示するページを作成します。
ページの作成
ProductsFront/Backendから共通のHTMLを抜き出します。
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 |
// pages/Products.tsx const Products = () => { return ( <div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3"> <div className="col"> <div className="card shadow-sm"> <svg className="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title> <rect width="100%" height="100%" fill="#55595c"/> <text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text> </svg> <div className="card-body"> <p className="card-text"> This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer. </p> <div className="d-flex justify-content-between align-items-center"> <div className="btn-group"> <button type="button" className="btn btn-sm btn-outline-secondary">View</button> <button type="button" className="btn btn-sm btn-outline-secondary">Edit</button> </div> <small className="text-muted">9 mins</small> </div> </div> </div> </div> </div> ) } export default Products |
このProductsコンポーネントをFront/Backendページから呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// pages/ProductsFrontend.tsx import Layout from '../components/Layout' import Products from '../pages/Products' const ProductsFrontend = () => { return ( <Layout> <Products /> </Layout> ) } export default ProductsFrontend |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// pages/ProductsBackend.tsx import Layout from '../components/Layout' import Products from '../pages/Products' const ProductsBackend = () => { return ( <Layout> <Products /> </Layout> ) } export default ProductsBackend |
Products APIへリクエストを送る
ProductsFrontend/BackendページからProducts APIへリクエストを送る処理を実装します。
Products APIは、以前以下の記事で実装しました。
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 |
// pages/ProductsFrontend.tsx import { useEffect, useState } from 'react' import { Product } from '../models/product' import Layout from '../components/Layout' import Products from '../pages/Products' import axios from 'axios' const ProductsFrontend = () => { const backendUrl = 'products/frontend' const [products, setProducts] = useState<Product[]>([]) useEffect(() => { ( async () => { const {data} = await axios.get(backendUrl) if (data) { setProducts(data) } } )() }, []) return ( <Layout> <Products products={products} /> </Layout> ) } export default ProductsFrontend |
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 |
// pages/ProductsBackend.tsx import { useEffect, useState } from 'react' import { Product } from '../models/product' import Layout from '../components/Layout' import Products from '../pages/Products' import axios from 'axios' const ProductsBackend = () => { const backendUrl = 'products/backend' const [products, setProducts] = useState<Product[]>([]) useEffect(() => { ( async () => { const {data} = await axios.get(backendUrl) if (data.data) { setProducts(data.data) } } )() }, []) return ( <Layout> <Products products={products} /> </Layout> ) } export default ProductsBackend |
PropsでProductsページにデータ渡す
Propsを使って、取得したデータをProductsページに渡してあげましょう。
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 |
// pages/Products.tsx import { Product } from '../models/product' const Products = (props: { products: Product[] }) => { return ( <div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3"> {props.products.map(product => { return ( <div className="col"> <div className="card shadow-sm"> <img src={product.image} height={200} /> <div className="card-body"> <p className="card-text"> {product.title} </p> <div className="d-flex justify-content-between align-items-center"> <small className="text-muted">${product.price}</small> </div> </div> </div> </div> ) })} </div> ) } export default Products |
検証
画像イメージがないので味気ない感じになりましたが、とりあえず成功ですね。
次回
次回は、検索機能の実装をしましょう。
Reactまとめ
参考書籍
ソースコード
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
// components/Nav.tsx import { Dispatch } from 'react' import { connect } from 'react-redux' import { User } from '../models/user' import { Link, NavLink } 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={'/rankings'} className="btn me-2">Rankings</Link> <Link to={'/stats'} className="btn me-2">Stats</Link> <Link to={'/profile'} 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> <NavLink to={'/'} activeClassName='link-dark' exact className="nav-link px-2 link-secondary"> Frontend </NavLink> </li> <li> <NavLink to={'/backend'} activeClassName='link-dark' className="nav-link px-2 link-secondary"> Backend </NavLink> </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 21 22 23 24 25 26 |
// App.tsx import { BrowserRouter, Route } from 'react-router-dom' import ProductsFrontend from './pages/ProductsFrontend' import ProductsBackend from './pages/ProductsBackend' import Login from './pages/Login' import Register from './pages/Register' import Profile from './pages/Profile' import Stats from './pages/Stats' import Rankings from './pages/Rankings' import './App.css'; function App() { return ( <BrowserRouter> <Route path={'/'} exact component={ProductsFrontend} /> <Route path={'/backend'} exact component={ProductsBackend} /> <Route path={'/login'} component={Login} /> <Route path={'/register'} component={Register} /> <Route path={'/profile'} component={Profile} /> <Route path={'/stats'} component={Stats} /> <Route path={'/rankings'} component={Rankings} /> </BrowserRouter> ); } export default App; |
Products.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 |
// pages/Products.tsx import { Product } from '../models/product' const Products = (props: { products: Product[] }) => { return ( <div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3"> {props.products.map(product => { return ( <div className="col"> <div className="card shadow-sm"> <img src={product.image} height={200} /> <div className="card-body"> <p className="card-text"> {product.title} </p> <div className="d-flex justify-content-between align-items-center"> <small className="text-muted">${product.price}</small> </div> </div> </div> </div> ) })} </div> ) } export default Products |
ProductsFrontend.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 |
// pages/ProductsFrontend.tsx import { useEffect, useState } from 'react' import { Product } from '../models/product' import Layout from '../components/Layout' import Products from '../pages/Products' import axios from 'axios' const ProductsFrontend = () => { const backendUrl = 'products/frontend' const [products, setProducts] = useState<Product[]>([]) useEffect(() => { ( async () => { const {data} = await axios.get(backendUrl) if (data) { setProducts(data) } } )() }, []) return ( <Layout> <Products products={products} /> </Layout> ) } export default ProductsFrontend |
ProductsBackend.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 |
// pages/ProductsBackend.tsx import { useEffect, useState } from 'react' import { Product } from '../models/product' import Layout from '../components/Layout' import Products from '../pages/Products' import axios from 'axios' const ProductsBackend = () => { const backendUrl = 'products/backend' const [products, setProducts] = useState<Product[]>([]) useEffect(() => { ( async () => { const {data} = await axios.get(backendUrl) if (data.data) { setProducts(data.data) } } )() }, []) return ( <Layout> <Products products={products} /> </Layout> ) } export default ProductsBackend |
コメントを残す
コメントを投稿するにはログインしてください。