🐸

next.js等を使用して検索画面を作成しました。

2023/12/22に公開

はじめに

現在取り組んでいるプロジェクトでnext.js等を使用したSNSを作成しています。
私が担当している画面の中でタブ付き検索画面が一番大変だったので記事にしたいと思います。

概要

next.js等を使用してデータベースに登録されてる作品やユーザーを検索できるタブ付き検索画面の作成。

  • 使用言語
    TypeScript

  • 使用ツール
    next.js
    react
    mantine

勉強になったこと

  • Suspenseとkeyの組み合わせ
  • searchParamsについて
  • useSelectedLayoutSegmentについて

完成物


トップ画面

作品検索画面
トップ画面の検索バーに検索ワードを入れ、検索ボタンを押すと、ローディングした後結果を表示します。
デフォルトは作品の検索結果を表示します。

ユーザー検索画面
タブをユーザーに変更するとユーザーの検索をすることが出来ます。

コードと解説

1. 作品検索

ここは作品の検索が出来る検索ページです

全体のコード
//作品の検索画面

import React, { Suspense } from 'react';
import { SearchResultList } from './SearchResultList';
import { Loader } from '@/components/Loader';
import { Box, Center } from '@mantine/core';

interface PageProps {
    searchParams: {
        q?: string
    }
}
const ArtSearchPage = async ({ searchParams }: PageProps) => {
    const query = searchParams.q ?? ""
    return (
        <Box my="md">
            {query.length >= 1
                ? <Suspense key={query} fallback={
                    <Center>
                        <Loader />
                    </Center>
                } >
                    <SearchResultList query={query} />
                </Suspense>
                : <div>検索文字列を入れて下さい。</div>
            }
        </Box>
    );
};

export default ArtSearchPage;

以下の内容についてお話します。

  • Suspenseとkeyの組み合わせ
  • searchParamsについて

Suspenseとkeyの組み合わせ

<Suspense key={query} /> ←これを使うことによりローディング状態を表示することが出来ました。

page.tsx
<Suspense key={query} fallback={
    // ローディング時に表示されるコンポーネントやメッセージ
}>
    <SearchResultList query={query} />
</Suspense>
  • key とは ...
    Suspenseコンポーネントが異なるクエリに基づいて何度も再利用されることを確認するためのもの。
    React 内部でコンポーネントの一致を確認するために使用され、異なるキーが指定されると React は異なるコンポーネントとして処理をする。これにより、異なる非同期データがロードされるたびに Suspense コンポーネントが再度マウントされることを確認することが出来る。

ここでは、検索クエリ (query) が変更されたときに異なるキーを指定することで、新しい検索結果を再取得し、Suspense 内でローディング状態を表示している。

他にも非同期処理を遅延させることによりローディング状態を表示できるuseDeferredValueというものがある。
https://ja.react.dev/reference/react/useDeferredValue

  • Suspenseとは ...
    Suspense は React の機能であり、非同期データの読み込み中にローディング状態を宣言的に扱うためのもの。
  • それらを組み合わせて
    React コンポーネント内で非同期データを扱う場合、Suspense を使用することで、非同期データがまだ利用できない場合のローディング表示をすることが出来る。

searchParamsについて

page.tsx
const query = searchParams.q ?? ""

Next.jsの機能でsearchParamsが取得できます。Next.jsはReactベースのフレームワークで、サーバーサイドレンダリングやルーティング、prefetchingなどの機能を提供します。searchParams のようなルーティング関連の情報は、Next.js のルーティングシステムによって提供され、ページコンポーネント内で取得できるようになります。


React と Next.js は密接に統合されており、React のコンポーネントを使用して UI を構築する際に、Next.js の追加の機能を利用することができます。
なので今回はReactのSuspenseとNext.jsのルーティング情報などを組み合わせて、ローディング状態を宣言的に管理しました。

2. 共通部分

ここはユーザーが検索したときにタブで(作品かユーザーか)分けられる部分で、ユーザーが操作する必要があるコンポーネントなので"use client"コンポーネントにしています。

全体のコード
layout.tsx
"use client"

import SearchBar from "@/components/SearchBar"
import { Space, Tabs } from "@mantine/core"
import Link from "next/link"
import { useSelectedLayoutSegment } from "next/navigation"
import { ReactNode } from "react"
import { MdStarBorder } from "react-icons/md";
import { LiaGrinStarsSolid } from "react-icons/lia";
import styles from "./layout.module.css"

interface PageProps {
    children: ReactNode
}
const SaearchLayout = ({ children }: PageProps) => {
    const segment = useSelectedLayoutSegment() as "art" | "user"
    return (
        <div>
            <SearchBar
                type={segment}
            />
            <Tabs value={segment}>
                <Tabs.List>
                    <Link href="/search/art">
                        <Tabs.Tab value="art" leftSection={<MdStarBorder />} className={styles.tab} >
                            作品
                        </Tabs.Tab>
                    </Link>
                    <Link href="/search/user">
                        <Tabs.Tab value="user" leftSection={<LiaGrinStarsSolid />} className={styles.tab}>
                            ユーザー
                        </Tabs.Tab>
                    </Link>
                </Tabs.List>
            </Tabs>

            {children}
        </div>
    )
}
export default SaearchLayout

layout.tsx
 const segment = useSelectedLayoutSegment() as "art" | "user"

useSelectedLayoutSegment()について

Next.jsの next/navigation モジュールで提供されるフックの一つです。
このフックは、現在のレイアウトセグメントを取得するために使用されます。

Next.js では、useSelectedLayoutSegment を使用して、ユーザーが選択したレイアウトセグメント(通常はページやコンポーネントの一部)を取得できます。
これにより、アプリケーション内で異なるセクションに対して異なるレイアウトや挙動を設定することができます。

3. ユーザー検索

ここはユーザーが検索バーに入力する部分で、ユーザーが操作する必要があるコンポーネントなので"use client"コンポーネントにしています。

全体のコード
SearchBar.tsx
//SearchBar
"use client"

import React, { useState } from 'react';
import { Flex, TextInput, ActionIcon } from '@mantine/core';
import { IoMdSearch } from 'react-icons/io';
import styles from "./page.module.css"
import Link from 'next/link';

// onSearchの型を定義
interface SearchBarProps {
    defaultValue?: string
}

const SearchBar: React.FC<SearchBarProps> = ({ defaultValue = "" }) => {
    const [searchInput, setSearchInput] = useState(defaultValue);
    const isValidSearchInput = searchInput.trim().length !== 0

    return (
        <Flex justify="center" align="center" mt="xl" mb="xl">
            <TextInput
                placeholder="作品やユーザを検索"
                value={searchInput}
                onChange={(e) => setSearchInput(e.target.value)}
            />
            <Link href={`/search/art?q=${searchInput}`}>
                <ActionIcon >
                    <検索ボタン />
                </ActionIcon>
            </Link>
        </Flex >
    );
};

export default SearchBar;
SearchBar.tsx
const [searchInput, setSearchInput] = useState(defaultValue);
  • useStateフックの利用
    関数コンポーネント内でstate(状態)を管理するために使用されています。
    searchInput というstate(状態)変数は、入力フィールドの値を保持します。
SearchBar.tsx
<Link href={`遷移先${searchInput}`}>
    <ActionIcon>
        <検索ボタン />
    </ActionIcon>
</Link>
  • Link コンポーネントの使用
    Link コンポーネントは Next.js のルーティングをサポートし、クライアントサイドでのナビゲーションを提供します。
    ユーザーが検索ボタンをクリックしたときに、指定された URL に遷移することが出来ます。
  • href プロパティの使用
    href プロパティは、リンクの遷移先を指定します。
    検索ボタンがクリックされると、searchInput の現在の値をクエリパラメータとして検索結果ページに遷移します。これにより、ユーザーが入力した検索ワードが検索結果ページに渡されます。

まとめ

  • 画面を作る上では、難しかったが設計書通りの画面を作る事が出来ました。
    あまり理解していない所があるので次は理解しながら作れるようにしたいと思います。

  • 記事を書く上では、エラーの解決策や詰まったところなどあったがしっかりメモを取っていなかったので次はもっと詳しく記録していきたいなと思いました。

めがね〜ず

Discussion