🧑‍💻

React + Nextでdebounce 機能を使った検索機能の実装

2022/07/23に公開

はじめに

初めまして。
初執筆の記事となります。どうぞよろしくお願いいたします!!

現在、会社のテックブログをリニューアルしています。
よくある機能ですが、検索窓にキーワードを入力し、入力されたキーワードが含まれるブログ記事をリアルタイムで取得する機能を作りました。

機能概要

  • 検索窓にキーワードを入力し、入力が終わったら、そのキーワードをAPIのクエリパラメータにのせてAPIをコールする
  • 検索窓でEnterキーが押下された際のイベントも検知し、APIをコールする
  • 検索結果(キーワードが含まれたURL)をシェアできるようにしたい

環境

  • React 18.0.0
  • Next.js 12.1.4

debounceを利用したカスタムフックの作成

  • debounce(デバウンス)とは、入力が指定した時間止まった時に入力が完了したと作用させることです
  • チェンジイベントで都度拾って、APIをコールする方法でも検索機能は満たせますが、サーバへのアクセスや負荷が増加するため、利用者様の入力が止まった際の値を返却するような処理を作りました
  • 先人の方たちの記事などを参考にさせていただきました

参考記事

https://zenn.dev/luvmini511/articles/4924cc4cf19bc9

カスタムフック

src/hooks/useDebouncedKeyword.tsx
import React, { useEffect, useState } from "react";

type Props = {
  value: string;
  delay?: number;
};

export const useDebouncedKeyword = ({ value, delay = 1000 }: Props) => {
  const [debouncedValue, setDebouncedValue] = useState<string>(value ?? "");

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debouncedValue;
};

利用例

キーワードの入力及び制御コンポーネント

  • コードを一部抜粋しています。
  • コンポーネントの設計として、主にロジックを責務とするContainer層、ビューを責務とするComponent層で実装しています。
src/components/helper/SearchBox/SearchBoxContainer.tsx
const SearchBoxContainer: React.FC = () => {

  const [keyword, setKeyword] = useState((router.query.q as string) || "");
  const debouncedKeyword = useDebouncedKeyword({ value: keyword, delay: 1000 });
  const handleChangKeyword = (e: React.ChangeEvent<HTMLInputElement>) => {
    setKeyword(e.target.value);
  };

  useEffect(() => {
    // キーワードの変更を検知して、ブログ検索画面へ遷移させる
    if (keyword && debouncedKeyword) {
      transitionBlogSearchPage(debouncedKeyword);
    }
  }, [debouncedKeyword, keyword]);

  return (
    <SearchBox
      keyword={keyword}
      onChangeKeyword={handleChangKeyword}
      searchBlogs={searchBlogs}
    />
  );
};

export default SearchBoxContainer;

ローカルstateのkeywordをカスタムフックであるuseDebouncedKeywordに渡しています。
入力が確定したら、その値をキーワードにして、/search?q=**キーワード**router.pushさせています

pageコンポーネント

一部、直接関係のない部分もありますが、コードの抜粋です

src/pages/search.tsx
const SearchPage: NextPage<TopPageProps> = ({ popularBlogPost }) => {
  const router = useRouter();

  const keyword = (router.query.q as string | undefined) || "";
  const searchBlogFetcher = (url: string): Promise<Blogs> =>
    fetch(url).then((res) => res.json());
  const { data } = useSWR<Blogs>(
    `/api/blog/search?keyword=${keyword}`,
    searchBlogFetcher,
    {
      onError: (error) => {
        router.push("/error");
      },
    },
  );

  return (
    <>
      <Head title="ブログ一覧" />
      <Layout>
        <SearchBlogResult blogs={data} popularBlogPost={popularBlogPost} />
      </Layout>
    </>
  );
};
  • URLのクエリパラメータからキーワードを取得し、swrを使って、APIをコールしています
  • APIのレスポンス結果をコンポーネントのpropsに渡して、リアルタイムで描画するという仕掛けとしています

実装イメージ

おわりに

以前、別のアプリケーション開発時にdebounce機能を使ったコードを見たことがあったので、そちらの記憶などを頼りに、参考にさせていただいた記事などを基に今回は自身で実装してみると、より理解が深まりました!!

なお、ブログに関しては、BEは、microCMSを活用させていただいており、開発体験が素晴らしいですね!!

以上です。
本記事が何かの一助になれば幸いです。

Discussion