こんにちは、KOUKIです。
Reactで画面開発を行なっています。
今回は、検索機能の実装を行いましょう。
尚、「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
前回
事前準備
ファイル
1 |
touch react-ambassador/src/models/filters.ts |
検索機能
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 |
// pages/Products.tsx import { Product } from '../models/product' const Products = (props: { products: Product[] }) => { return ( <> <div className="col-md-12 mb-4 input-group"> <input type="text" className="form-control" placeholder="Search" /> </div> <div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3"> {props.products.map(product => { return ( <div className="col" key={product.id}> <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 |
画面を表示してみましょう。

filterモデルの作成
以前、以下の記事でSearch APIを実装しました。
このAPIでは、URLの末尾に検索用のキー「q=文字列」を指定してリクエストを送り、フィルタリングした商品を受け取ることができます。
1 2 |
// 例: query tkcで検索 http://localhost:8000/api/ambassador/products/backend?q=tkc |
filterモデルは、このqを型として表現します。
1 2 3 4 |
// models/filters.ts export interface Filters { q: string } |
この型は、以下のように使用します。
1 2 3 4 5 6 7 8 9 10 11 12 |
// pages/Products.tsx import { Product } from '../models/product' import { Filters } from '../models/filters' const Products = (props: { products: Product[] filters: Filters, setFilters: (filters: Filters) => void }) => { ... } } |
Search APIへリクエストを送る
まず、Products.tsxにsearchメソッドを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// pages/Products.tsx ... const Products = (props: { products: Product[] filters: Filters, setFilters: (filters: Filters) => void }) => { const search = (q: string) => { props.setFilters({ q }) } ... } export default Products |
「setFilters」は、useStateで作成した状態管理用の関数です。filtersの状態を変更する役割を持ちます。
これは、検索フォームに文字列が入力された時に発火するようにします。
1 2 3 4 5 6 7 8 9 10 |
// pages/Products.tsx <div className="col-md-12 mb-4 input-group"> <input type="text" className="form-control" placeholder="Search" onChange={e => search(e.target.value)} /> </div |
次に、ProductsFrontend/Backendのコードに、queryを設定するコードを実装します。
Frontend側は、一度全てのプロダクトコードを取得してから、Filterをかけます。
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 |
// pages/ProductsFrontend.tsx import { useEffect, useState } from 'react' import { Product } from '../models/product' import { Filters } from '../models/filters' import Layout from '../components/Layout' import Products from '../pages/Products' import axios from 'axios' const ProductsFrontend = () => { const frontendUrl = 'products/frontend' const [filterProducts, setFilterProducts] = useState<Product[]>([]) const [allProducts, setAllProducts] = useState<Product[]>([]) const [filters, setFilters] = useState<Filters>({ q: '' }) useEffect(() => { ( async () => { const {data} = await axios.get(frontendUrl) if (data) { setAllProducts(data) setFilterProducts(data) } } )() }, []) useEffect(() => { let products = allProducts.filter( p => p.title.toLocaleLowerCase().indexOf( filters.q.toLowerCase()) >= 0 || p.description.toLowerCase().indexOf( filters.q.toLowerCase()) >= 0) console.log({products}) setFilterProducts(products) }, [filters]) return ( <Layout> <Products products={filterProducts} filters={filters} setFilters={setFilters} /> </Layout> ) } export default ProductsFrontend |
バックエンド側は、「http://localhost:8000/api/ambassador/products/backend?q=tkc」へリクエストを送れば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 |
// pages/ProductsBackend.tsx import { useEffect, useState } from 'react' import { Product } from '../models/product' import { Filters } from '../models/filters' 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[]>([]) const [filters, setFilters] = useState<Filters>({ q: '' }) useEffect(() => { ( async () => { const arr = [] if (filters.q) { arr.push(`q=${filters.q}`) } const {data} = await axios.get( backendUrl + '?' + arr.join('&')) if (data.data) { setProducts(data.data) } } )() }, [filters]) return ( <Layout> <Products products={products} filters={filters} setFilters={setFilters} /> </Layout> ) } export default ProductsBackend |
検証
OKですね。
次回
次回は、ソート機能の実装をしましょう。
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 |
// pages/Products.tsx import { Product } from '../models/product' import { Filters } from '../models/filters' const Products = (props: { products: Product[] filters: Filters, setFilters: (filters: Filters) => void }) => { const search = (q: string) => { props.setFilters({ q }) } return ( <> <div className="col-md-12 mb-4 input-group"> <input type="text" className="form-control" placeholder="Search" onChange={e => search(e.target.value)} /> </div> <div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3"> {props.products.map(product => { return ( <div className="col" key={product.id}> <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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// pages/ProductsFrontend.tsx import { useEffect, useState } from 'react' import { Product } from '../models/product' import { Filters } from '../models/filters' import Layout from '../components/Layout' import Products from '../pages/Products' import axios from 'axios' const ProductsFrontend = () => { const frontendUrl = 'products/frontend' const [filterProducts, setFilterProducts] = useState<Product[]>([]) const [allProducts, setAllProducts] = useState<Product[]>([]) const [filters, setFilters] = useState<Filters>({ q: '' }) useEffect(() => { ( async () => { const {data} = await axios.get(frontendUrl) if (data) { setAllProducts(data) setFilterProducts(data) } } )() }, []) useEffect(() => { let products = allProducts.filter( p => p.title.toLocaleLowerCase().indexOf( filters.q.toLowerCase()) >= 0 || p.description.toLowerCase().indexOf( filters.q.toLowerCase()) >= 0) console.log({products}) setFilterProducts(products) }, [filters]) return ( <Layout> <Products products={filterProducts} filters={filters} setFilters={setFilters} /> </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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
// pages/ProductsBackend.tsx import { useEffect, useState } from 'react' import { Product } from '../models/product' import { Filters } from '../models/filters' 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[]>([]) const [filters, setFilters] = useState<Filters>({ q: '' }) useEffect(() => { ( async () => { const arr = [] if (filters.q) { arr.push(`q=${filters.q}`) } const {data} = await axios.get( backendUrl + '?' + arr.join('&')) if (data.data) { setProducts(data.data) } } )() }, [filters]) return ( <Layout> <Products products={products} filters={filters} setFilters={setFilters} /> </Layout> ) } export default ProductsBackend |
filters.ts
1 2 3 4 |
// models/filters.ts export interface Filters { q: string } |
コメントを残す
コメントを投稿するにはログインしてください。