🔍

【Tips】TanStack Query で incremental search する

2023/12/12に公開

React と TanStack Query で incremental searchしたくなった時の備忘録です。

対象読者

  • TanStack Query (useQuery) の基本的な使用方法を知っている方

普段通り実装すると よくない挙動をする

普段と同じように useQuery に keyと取得関数を渡しただけでは検索文字列を変えるたびにdataがnullになってしまい よくない挙動になってしまいます。

const [query, setQuery] = useState("");
const searchResult = useQuery({
  queryKey: ["items", "search", query],
  queryFn: async () => {
    // サーバから検索結果を取得
    return await searchItems(query);
  },
});

<input
  type="text"
  value={query}
  onChange={(e) => setQuery(e.target.value)}
/>

<ul>
  {searchResult.data?.map((item) => <li key={item}>{item}</li>) ?? "..."}
</ul>

検索文字列(query)を変えるたびに "..." が表示されてしまっています。

これは検索文字列が変わるたびに queryKey が変わることで searchResult.data がnullになってしまうためです。

リセットしないようにする

文字列を打ち込んでも表示したアイテムが消えないようにするには useQueryの placeholderData: keepPreviousData オプションを利用するといい感じになります。

  const [query, setQuery] = useState("");
  const searchResult = useQuery({
    queryKey: ["items", "search", query],
    queryFn: async () => {
      return await searchItems(query);
    },
+   placeholderData: keepPreviousData,
  });
  
  <input
    type="text"
    value={query}
    onChange={(e) => setQuery(e.target.value)}
  />
  
  <ul>
-   {searchResult.data?.map((item) => <li key={item}>{item}</li>) ?? "..."}
+   {searchResult.data?.map((item) => <li key={item}>{item}</li>)}
+   {searchResult.isFetching && "..."}
  </ul>

以下のドキュメントから着想を得ました。

https://tanstack.com/query/latest/docs/react/guides/paginated-queries#better-paginated-queries-with-placeholderdata

まとめ

TanStack Queryで incremental search したいときに placeholderData: keepPreviousData を指定すると良さそうな挙動になりました。

incremental search に限らず、「キーが変わっても一旦は前のデータを表示しといて欲しいんだけど」みたいな状況に利用できそうですね。

また、queryをdebounceするなどの処理も挟むとサーバにも優しくなってより良さそうです。

めがね〜ず

Discussion