use-search-query-sync.ts1.7 KBView on GitHub
import { useEffect } from 'react';
import { useQueryState } from 'nuqs';

import { buildSearchQuery } from '@/modules/search/build-search-query';
import { useCedarStore } from '@/modules/store';
import { getMainSearchTerm } from '@/lib/utils';

/**
 * Mirror the `?search=` URL param into the search slice — the store value `useThreads` filters
 * the list by.
 *
 * This lives with the LIST rather than with the search input, and that placement is the whole
 * point. It used to be an effect inside `SearchInput`, which mail.tsx mounts only while
 * `isSearchOpen || !!searchQuery`. `isSearchOpen` is component state that resets to false on
 * remount and `?search=` is dropped by any navigation that rewrites the URL, so the input could
 * unmount with a compiled query still in the store — and `useCedarStore` is a module singleton
 * that outlives it. Nothing was then left to clear the filter: the list stayed narrowed to a
 * search whose box was no longer on screen (<email>, staging, 2026-09-08 —
 * `mail.listThreads` re-ran her `thiago` search every five minutes for three hours while she
 * looked at what she thought was her inbox).
 *
 * Call it unconditionally, next to the list it filters.
 */
export function useSearchQuerySync(): void {
  const [searchQuery] = useQueryState('search');
  const setSearchState = useCedarStore((state) => state.setSearchState);

  useEffect(() => {
    if (searchQuery) {
      setSearchState({
        value: buildSearchQuery(searchQuery, 'google'),
        highlight: getMainSearchTerm(searchQuery),
      });
    } else {
      setSearchState({ value: '', highlight: '' });
    }
  }, [searchQuery, setSearchState]);
}