Reactで検索機能つき一覧画面をスクラッチで作る

目次

はじめに

職業プログラマであれば、一覧画面は避けては通れません。

管理画面があるシステムであれば、ほぼ間違いなく一覧画面を持っています。
難易度も比較的低いので、新人さんに回ってきやすい機能といえるでしょう。

かくいう私も、初めての実務ではJSPサーブレットで一覧画面を作りました。

まだ実装したことがないという方は、ぜひこのタイミングで一度作っておきましょう。
そうすれば実務で回ってきても安心です。
ちゃちゃっと実装して、上司に「お?もうできたの?」と言わせちゃいましょう。

実物はこちら

あわせて読みたい
React Recipe Reactアプリのレシピ集

リポジトリはこちら

GitHub
GitHub - hakoratory/react-recipe Contribute to hakoratory/react-recipe development by creating an account on GitHub.

概要

この記事を読むことで、以下のような画面が作れるようになります。

以下の仕様を想定して作成しています。

  • 従業員の一覧を表示する
  • 画面の初期表示時は検索された状態
  • 「名前」で絞り込みが可能
  • 「名前」の入力欄でバリデーションエラーがあるときは検索ボタンは押せない

実装

先にコード全容です。

'use client'

import '../style.css'
import {Box, Button} from "@mui/material";
import {Input} from "@/components/input";
import {useTextInput} from "@/hooks/use-text-input";
import {tableUsers} from "@/data/data";
import {FetchTableUsersRequestType, TableUser} from "@/types/table";
import {useEffect, useState} from "react";

const fetchUsers = async (param: FetchTableUsersRequestType): Promise<Array<TableUser>> => {
    if (process.env.NEXT_PUBLIC_USE_MOCK_DATA === 'true') {
        return tableUsers.filter(user => user.name.indexOf(param.name ?? '') > -1)
    } else {
        const response = await fetch('/api/table/users', {
            method: 'POST',
            body: JSON.stringify(param)
        })
        const {users} = await response.json()
        return users
    }
}

export default function TableScratch() {
    const [searchWord, setSearchWord] = useTextInput()
    const [tableUserData, setTableUserData] = useState<Array<TableUser>>([])

    useEffect(() => {
        (async () => {
            const newUsers = await fetchUsers({ name: null })
            setTableUserData(newUsers)
        })()
    }, [])

    const handleClick = async () => {
        const newUsers = await fetchUsers({ name: searchWord.value })
        setTableUserData(newUsers)
    }

    return (
        <main>
            <Box m={2}>
                <Box display="flex">
                    <Box>
                        <Input label="名前" errors={searchWord.errors} onChange={(e) => setSearchWord(e.target.value)} />
                    </Box>
                    <Box ml={3} pt={3}>
                        <Button
                            type="button"
                            variant="contained"
                            disabled={searchWord.errors.length > 0}
                            onClick={handleClick}
                        >
                            検索
                        </Button>
                    </Box>
                </Box>
            </Box>
            <Box m={2}>
                <table>
                    <thead>
                    <tr>
                        <th>名前</th>
                        <th>所属</th>
                        <th>好きなもの</th>
                    </tr>
                    </thead>
                    <tbody>
                    {
                        tableUserData.map((user) => (
                            <tr key={user.id}>
                                <td>{user.name}</td>
                                <td>{user.department}</td>
                                <td>{user.favoriteThings}</td>
                            </tr>
                        ))
                    }
                    </tbody>
                </table>
            </Box>
        </main>
    )
}

各部分の解説

一覧取得処理

一覧データの取得は fetchUsers 関数で行っています。
初期表示時にはすでに一覧が表示されているようにしたいので、useEffect フックを使用して初期表示時に検索処理を実行しています。

const fetchUsers = async (param: FetchTableUsersRequestType): Promise<Array<TableUser>> => {
    if (process.env.NEXT_PUBLIC_USE_MOCK_DATA === 'true') {
        return tableUsers.filter(user => user.name.indexOf(param.name ?? '') > -1)
    } else {
        const response = await fetch('/api/table/users', {
            method: 'POST',
            body: JSON.stringify(param)
        })
        const {users} = await response.json()
        return users
    }
}

export default function TableScratch() {
    const [searchWord, setSearchWord] = useTextInput()
    const [tableUserData, setTableUserData] = useState<Array<TableUser>>([])

    useEffect(() => {
        (async () => {
            const newUsers = await fetchUsers({ name: null })
            setTableUserData(newUsers)
        })()
    }, [])

ハイライトした箇所でAPI呼び出しをしています。
ここで呼んでいるAPIは Next.js の API Routes で実装しています。

以下はそのコードです。

import { NextRequest, NextResponse } from "next/server"
import {tableUsers} from "@/data/data";
import {FetchTableUsersRequestType, TableUser} from "@/types/table";

export async function POST(request: NextRequest): Promise<NextResponse> {
    const params: FetchTableUsersRequestType = await request.json()
    const filteredUsers: TableUser[] = tableUsers.filter(user => user.name.indexOf(params.name ?? '') > -1)
    return NextResponse.json({
        users: filteredUsers
    })
}

Reactレシピシリーズは GitHub Pages でホスティングしているので、公開サイトではこのAPIは使えません。
なので、モックデータから取得するようにしています。

検索キーワードの入力欄と state

一覧を「名前」で絞り込むための入力欄を設けています。

このUIと state は、過去の記事で作成したコンポーネントとカスタムフックを使用しています。

export default function TableScratch() {
    const [searchWord, setSearchWord] = useTextInput()
    const [tableUserData, setTableUserData] = useState<Array<TableUser>>([])

========== 中略 ==========

    return (
        <main>
            <Box m={2}>
                <Box display="flex">
                    <Box>
                        <Input label="名前" errors={searchWord.errors} onChange={(e) => setSearchWord(e.target.value)} />
                    </Box>

今回は最大30字までのバリデーションをするようになっています。
バリデーションエラーがある場合は、検索ボタンを非活性表示になります。

このバリデーション機能を作ってみたい方は、こちらの記事も併せてご覧ください。

一覧データを state にセットする

画面の初期表示時と検索ボタンを押下時に検索処理が実行され、検索結果を state に反映します。
特に変わったところはなく、一般的な useState の使い方ですね。

export default function TableScratch() {
    const [searchWord, setSearchWord] = useTextInput()
    const [tableUserData, setTableUserData] = useState<Array<TableUser>>([])

    useEffect(() => {
        (async () => {
            const newUsers = await fetchUsers({ name: null })
            setTableUserData(newUsers)
        })()
    }, [])

    const handleClick = async () => {
        const newUsers = await fetchUsers({ name: searchWord.value })
        setTableUserData(newUsers)
    }

一覧データを出力する

一覧データが取得できたので出力しましょう。
繰り返しで出力する場合はkey 属性のセットを忘れないようにしましょう。

                <table>
                    <thead>
                    <tr>
                        <th>名前</th>
                        <th>所属</th>
                        <th>好きなもの</th>
                    </tr>
                    </thead>
                    <tbody>
                    {
                        tableUserData.map((user) => (
                            <tr key={user.id}>
                                <td>{user.name}</td>
                                <td>{user.department}</td>
                                <td>{user.favoriteThings}</td>
                            </tr>
                        ))
                    }
                    </tbody>
                </table>

おわりに

いかがだったでしょうか?
今回は、実務ではお馴染みの一覧画面の実装をしてみました。

職業プログラマであれば、一覧画面を実装する機会は多いと思うので、ぜひ今のうちにマスターしておきましょう。

ハコラトリは、Reactを習得したい駆け出しエンジニアを応援しています。

Reactレシピでは、これからもReactで色々なUIを作って紹介していきますので、「こんなUIを作ってみてほしい」「こんな場合はどうすれば?」といったアイディアを募集中です!
コメントお待ちしております。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

28歳のときに音楽家からエンジニアに転向。
WEB系のシステム開発(Java, C#, Vue, React など)に携わっています。

自分らしく力を発揮できて、自信を持って生きられる人を増やすための活動をしています。

コメント

コメントする

目次