こんにちは、KOUKIです。Reactで画面開発を行なっています。
前回は、Product作成機能を実装したので、今回は編集機能にチャレンジしましょう。
尚、「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
前回
Productの更新
編集ボタンをつける
Productページ上に更新ボタンをつけましょう。
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 |
// pages/products/Products.tsx ... const Products = () => { ... return ( <Layout> ... <Table className="table table-striped table-sm"> <TableHead>...</TableHead> <TableBody> {products.slice(page * perPage, (page + 1) * perPage).map(product => { return ( <TableRow key={product.id}> ... <TableCell> <Button variant="contained" color="primary" href={`/products/${product.id}/edit`} >Edit</Button> <Button variant="contained" color="secondary" onClick ={() => del(product.id)} >Delete</Button> </TableCell> </TableRow> ) })} </TableBody> <TableFooter> ... </TableFooter> </Table> </Layout> ) } export default Products |
Delete HTMLの上に、Edit HTMLを追加しただけです。hrefに指定しているルートは、このあと実装します。
1 2 3 4 5 |
<Button variant="contained" color="primary" href={`/products/${product.id}/edit`} >Edit</Button> |
ルートを追加
編集用のルートを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// App.tsx ... function App() { return ( <div className="App"> <BrowserRouter> ... <Route path={'/products/:id/edit'} component={ProductForm}></Route> </BrowserRouter> </div> ); } export default App; |
「:id」を指定しているので、呼び出し先で「props.match.params.id」と指定するとIDを取り出すことができます。
遷移確認
Editボタンを押下後、Product Formへ遷移するはずなので、確認してみましょう。


Product Formに編集データを表示する
編集ボタンからProduct Formを開いた時、編集用のデータが表示されるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// pages/products/ProductForm.tsx import { useEffect, useState, SyntheticEvent } from 'react' ... // Propsをつける const ProductForm = (props: any) => { ... const productGetEditUrl = `products/${props.match.params.id}` // ページが表示されたタイミングで、APIからデータを取得する useEffect(() => { ( async () => { if (props.match.params.id) { const { data } = await axios.get(productGetEditUrl) setTitle(data.title) setDescription(data.description) setImage(data.image) setPrice(data.price) } } )() }, []) ... } |
最初に、useEffectでProduct Formページを開いた時に、API側へIDに紐づくProduct情報を取得しました。
次に、取得した情報を画面上に表示します。これは、FormのFieldに「value」を付ければ、OKです。
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 |
// pages/products/ProductForm.tsx .... const ProductForm = (props: any) => { ... return( <Layout> <form onSubmit={submit}> <div className="mb-3"> <TextField label="title" value={title} onChange={e => setTitle(e.target.value)} /> </div> <div className="mb-3"> <TextField label="Description" value={description} rows={4} multiline onChange={e => setDescription(e.target.value)} /> </div> <div className="mb-3"> <TextField label="Image" value={image} onChange={e => setImage(e.target.value)} /> </div> <div className="mb-3"> <TextField label="Price" value={price} type="number" onChange={e => setPrice(Number(e.target.value))} /> </div> <Button variant="contained" color="primary" type="submit"> Submit </Button> </form> </Layout> ) } export default ProductForm |
表示確認
ページを開いた時に、FormのフィールドにProduct情報が表示されていたら成功です。

OKですね! 前回登録したProduct情報を表示しました。
Submitのリファクタリング
Product Formは、Productの作成と編集の両方を処理しなくてはなりません。そのため、以下のリファクタリングを行います。
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 |
// pages/products/ProductForm.tsx ... // Propsをつける const ProductForm = (props: any) => { ... const submit = async (e: SyntheticEvent) => { e.preventDefault() const data = { title, description, image, price } // もしIDが渡されていた場合、Edit機能を呼ぶ if (props.match.params.id) { await axios.put(productGetEditUrl, data) } else { await axios.post(productCreateUrl, data) } setRedirect(true) } if (redirect) { return <Redirect to={'/' + productCreateUrl} /> } return( .... } export default ProductForm |
編集確認
前回登録した「This is the most imcredible book int the world!」というデータですが、「imcredible」の部分に誤りがありまして、正しくは「incredible」でした。
ずっと気がかりだったので、ようやく修正できます。
OKですね!
次回
次回は、Order機能を実装します。
Reactまとめ
参考書籍
ソースコード
ここまで実装したソースコードを下記に記載します。
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 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 |
// pages/products/Products.tsx import { useState, useEffect } from 'react' import { Product } from '../../models/Product' import axios from 'axios' import { Button, Table, TableBody, TableRow, TableHead, TableCell, TableFooter, TablePagination } from '@material-ui/core' import Layout from '../../components/Layout' const Products = () => { const [products, setProducts] = useState<Product[]>([]) const productsUrl = '/products' const [page, setPage] = useState(0) const perPage = 10 useEffect(() => { ( async () => { const { data } = await axios.get(productsUrl) setProducts(data) } )() }, []) const del = async (id: number) => { if (window.confirm('商品を削除しますか?')) { await axios.delete(`${productsUrl}/${id}`) setProducts(products.filter(p => p.id !== id)) } } return ( <Layout> <div className="pt-3 pb-2 mb-3 border-bottom"> <Button href="/products/create" variant="contained" color="primary" > Add </Button> </div> <Table className="table table-striped table-sm"> <TableHead> <TableRow> <TableCell>#</TableCell> <TableCell>Image</TableCell> <TableCell>Title</TableCell> <TableCell>Description</TableCell> <TableCell>Price</TableCell> <TableCell>Actions</TableCell> </TableRow> </TableHead> <TableBody> {products.slice(page * perPage, (page + 1) * perPage).map(product => { return ( <TableRow key={product.id}> <TableCell>{product.id}</TableCell> <TableCell><img src={product.image} width={50}/></TableCell> <TableCell>{product.title}</TableCell> <TableCell>{product.description}</TableCell> <TableCell>{product.price}</TableCell> <TableCell> <Button variant="contained" color="primary" href={`/products/${product.id}/edit`} >Edit</Button> <Button variant="contained" color="secondary" onClick ={() => del(product.id)} >Delete</Button> </TableCell> </TableRow> ) })} </TableBody> <TableFooter> <TableRow> <TablePagination count={products.length} page={page} onChangePage={(e, newPage) => setPage(newPage)} rowsPerPageOptions={[]} rowsPerPage={perPage} ></TablePagination> </TableRow> </TableFooter> </Table> </Layout> ) } export default Products |
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 27 28 |
// App.tsx import './App.css'; import {BrowserRouter, Route} from 'react-router-dom' import {RedirectToUsers} from './components/RedirectToUsers' import Users from './pages/Users' import Login from './pages/Login' import Register from './pages/Register' import Links from './pages/Links' import Products from './pages/products/Products' import ProductForm from './pages/products/ProductForm' function App() { return ( <div className="App"> <BrowserRouter> <Route path={'/'} exact component={RedirectToUsers}></Route> <Route path={'/login'} component={Login}></Route> <Route path={'/register'} component={Register}></Route> <Route path={'/users'} exact component={Users}></Route> <Route path={'/users/:id/links'} component={Links}></Route> <Route path={'/products'} exact component={Products}></Route> <Route path={'/products/create'} component={ProductForm}></Route> <Route path={'/products/:id/edit'} component={ProductForm}></Route> </BrowserRouter> </div> ); } export default App; |
ProductForm.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 95 96 97 |
// pages/products/ProductForm.tsx import { useEffect, useState, SyntheticEvent } from 'react' import { Button, TextField } from '@material-ui/core' import Layout from '../../components/Layout' import axios from 'axios' import { Redirect } from 'react-router-dom' // Propsをつける const ProductForm = (props: any) => { const [title, setTitle] = useState('') const [description, setDescription] = useState('') const [image, setImage] = useState('') const [price, setPrice] = useState(0) const [redirect, setRedirect] = useState(false) const productCreateUrl = 'products' const productGetEditUrl = `products/${props.match.params.id}` // ページが表示されたタイミングで、APIからデータを取得する useEffect(() => { ( async () => { if (props.match.params.id) { const { data } = await axios.get(productGetEditUrl) setTitle(data.title) setDescription(data.description) setImage(data.image) setPrice(data.price) } } )() }, []) const submit = async (e: SyntheticEvent) => { e.preventDefault() const data = { title, description, image, price } // もしIDが渡されていた場合、Edit機能を呼ぶ if (props.match.params.id) { await axios.put(productGetEditUrl, data) } else { await axios.post(productCreateUrl, data) } setRedirect(true) } if (redirect) { return <Redirect to={'/' + productCreateUrl} /> } return( <Layout> <form onSubmit={submit}> <div className="mb-3"> <TextField label="title" value={title} onChange={e => setTitle(e.target.value)} /> </div> <div className="mb-3"> <TextField label="Description" value={description} rows={4} multiline onChange={e => setDescription(e.target.value)} /> </div> <div className="mb-3"> <TextField label="Image" value={image} onChange={e => setImage(e.target.value)} /> </div> <div className="mb-3"> <TextField label="Price" value={price} type="number" onChange={e => setPrice(Number(e.target.value))} /> </div> <Button variant="contained" color="primary" type="submit"> Submit </Button> </form> </Layout> ) } export default ProductForm |
コメントを残す
コメントを投稿するにはログインしてください。