AlgoliaのInstantsearch.js(React)を使った検索機能を、力技で特定ユーザーのみに提供する

この記事はAlgolia Advent Calendar2021、5日目の記事です。

やりたかったこと(背景)

  • 自分のサイトに、プレミアム機能的なものをつけてみたかった
  • プレミアム限定にできそうな機能で、やる気が出そうなものが思い浮かばなかった
  • 「すでにAlgoliaいれてるし、これでなにかするか」

やったこと

1、公式のサンプルを漁って土台を見つける

公式のドキュメントにある「未入力時に検索を実行しない」コードサンプルをベースにしました。

import algoliasearch from 'algoliasearch/lite';
import { InstantSearch, SearchBox, Hits } from 'react-instantsearch-dom';

const algoliaClient = algoliasearch(
  'undefined',
  'undefined'
);

const searchClient = {
  ...algoliaClient,
  search(requests) {
    if (requests.every(({ params }) => !params.query)) {
      return Promise.resolve({
        results: requests.map(() => ({
          hits: [],
          nbHits: 0,
          nbPages: 0,
          page: 0,
          processingTimeMS: 0,
        })),
      });
    }

    return algoliaClient.search(requests);
  },
};

const App = () => (
  <InstantSearch searchClient={searchClient} indexName="instant_search">
    <SearchBox />
    <Hits />
  </InstantSearch>
);

 ポイントは、「InstantSearchに渡すsearchClientオブジェクトの、searchの挙動を上書きすること」です。

サンプルコードは、「検索クエリがない場合、空の結果を返す」挙動をif文で表現しています。

const searchClient = {
  ...algoliaClient,
  search(requests) {
    if (requests.every(({ params }) => !params.query)) {
      return Promise.resolve({
        results: requests.map(() => ({
          hits: [],
          nbHits: 0,
          nbPages: 0,
          page: 0,
          processingTimeMS: 0,
        })),
      });
    }
...

つまり、ここの上書き条件をカスタムすれば、「ログインユーザーのみ動作する」などの挙動が実現できます。

2、React hookでクライアントの更新処理をいれる

アクセスするユーザーの状態によって挙動が変わるため、searchClientはReact Component内で定義する必要があります。そのためuseMemoなどを使う形に実装を変更します。


const noResult = {
    hits: [],
    nbHits: 0,
    nbPages: 0,
    page: 0,
    processingTimeMS: 0,
  };
  
  export const AlgoliaSearchProvider = ({ children, hasSubscription }) => {
    const searchClient = useMemo(() => {
      if (!hasSubscription) {
        /**
         * 無課金ユーザーは動かさない
         */
        return {
          search(requests) {
            return Promise.resolve({
                results: requests.map(() => noResult),
              });
          },
        };
      }
      /**
       * 課金ユーザーはAlgolia
       */
      return {
        search(requests, requestOptions) {
          if (requests.every(({ params }) => !params || !params.query)) {
            return Promise.resolve({
              results: requests.map(() => ({
                hits: [],
                nbHits: 0,
                nbPages: 0,
                page: 0,
                processingTimeMS: 0,
              })),
            });
          }
          return algoliaClient.search(requests, requestOptions);
        },
      };
    }, [hasSubscription]);
  
    return (
      <InstantSearch indexName="wp_posts_post" searchClient={searchClient}>
        <Configure clickAnalytics />
        {children}
      </InstantSearch>
    );
  };

かなりの力技ですが、これでユーザーの状態が変わると、searchの挙動も変わります。

これでログイン中ユーザーや課金ユーザーなど限定でAlgoliaを使った検索機能を提供できるようになりました。

ちなみに、かなり力技なので、いろいろ細かく設定をしたい場合や、Recommendなどを使いたい場合には、本当に動くか検証してから採用するようにしてください。

おまけ:検索クライアントを差し替えたい場合

「ただ検索させなくなるのは嫌だ」という場合、searchメソッド内で以下のようなコードを入れると、検索クエリを取得できます。

const searchWord = requests
.map(({ params }) => {
  if (!params || !params.query) return null;
  return params.query;
})
.join(" ");

あとはこれをAlgolia以外の検索APIに投げれば、たとえば「無料ユーザーはWP APIでお手軽に、課金してくれた人はAlgoliaで高機能検索体験を」みたいなこともできる(はず)です。

Comment