'How can i stop this infinite loop in my next.js app? (Search page via url params)

I want to create a search page that in driven by the url query. It includes params such as:

  • searchQuery e.g. "nike shoes"
  • page e.g. "1"

etc.

However im struggling at the moment with getting it work without heading into an infinite loop.

Here is my search page:

function Search() {
  const [listings, setListings] = useState<null | Array<ListingObject>>(null);
  const [noResultsListings, setNoResultsListings] = useState<
    Array<ListingObject>
  >([]);
  const [loading, setLoading] = useState("idle");
  const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false);
  const [paginationData, setPaginationData] = useState<PaginationData>({
    totalHits: 0,
    totalPages: 0,
  });
  const { query } = Router;

  function onFilterCheck(checked: Boolean, id: string, option) {
    let query = Router.query;
    if (checked) {
      if (query[id]) {
        query[id] += `,${option.value}`;
      } else {
        query[id] = `${option.value}`;
      }
    }
    if (!checked) {
      if (query[id] && query[id].toString().split(",").length > 1) {
        let param = query[id].toString();
        let array = param.split(",");
        query[id] = array.filter((param) => param !== option.value);
      } else {
        delete query[id];
      }
    }
    Router.push(
      {
        pathname: `/search`,
        query,
      },
      undefined,
      { shallow: true }
    );
  }

  const onSortByChanged = (sortByIndex: string) => {
    let { query } = Router;
    if (sortByIndex) {
      query.sortBy = sortByIndex;
      Router.push(
        {
          pathname: `/search`,
          query,
        },
        undefined,
        { shallow: true }
      );
    }
  };

  const search = useCallback(async (query) => {
    console.log("in search");
    if (loading === "idle") {
      console.log("inside IF");
      setLoading("loading");
      const readyQuery = await handleSearchQuery(query);
      let listingIndex = searchClient.initIndex(query.sortBy ?? "listings");
      const response = await listingIndex.search(readyQuery.searchQuery, {
        numericFilters: readyQuery.filters.numericFilters,
        facetFilters: readyQuery.filters.facetFilters,
        page: query.page - 1 ?? 0,
      });
      if (response) {
        const hits = response.hits;
        setPaginationData({
          totalPages: response.nbPages,
          totalHits: response.nbHits,
        });
        if (hits.length === 0) {
          const response = await getNewListings(3);
          if (response.success) {
            setNoResultsListings(response.listings);
          }
        }
        setListings(hits);
      }
      setLoading("idle");
    }
  }, []);

  const handlePageClick = useCallback(
    (pageNumber) => {
      console.log("page number from handlePageClick", pageNumber);
      if (loading === "idle") {
        let { query } = Router;
        query.page = (parseInt(pageNumber) + 1).toString();
        Router.push(
          {
            pathname: `/search`,
            query,
          },
          undefined,
          { shallow: true }
        );
        search(query);
        window.scrollTo(0, 0);
      }
    },
    [loading, search]
  );

  useEffect(() => {
    search(query);
  }, [search, query]);

  useEffect(() => {
    if (process.env.NODE_ENV === "production") {
      hotjar.initialize(
        parseInt(process.env.NEXT_PUBLIC_HOTJAR_ID),
        parseInt(process.env.NEXT_PUBLIC_HOTJAR_SV)
      );
    }
  }, []);

  if (!Router.query.page) {
    const { query } = Router;
    query.page = "1";
    Router.push({ pathname: "/search", query }, undefined, {
      shallow: true,
    });
  }

  return (
    <main className="mx-auto w-full transition-all duration-200 ease-in md:px-0">
      <section className="flex w-full flex-col pt-0 md:flex-row">
        <div className="hidden min-h-screen w-1/5 grid-cols-1 md:grid lg:grid-cols-1 lg:pr-4">
          {/* Desktop filters */}
          <form className="hidden h-full overflow-y-auto p-0 md:inline-block">
            <div className="relative z-30 flex h-full flex-col border-r pb-6 pr-6 md:z-0">
              <SearchSortBy onSortByChanged={onSortByChanged} />
              <SearchFilters
                mobileFiltersOpen={mobileFiltersOpen}
                setMobileFiltersOpen={() => {
                  setMobileFiltersOpen(false);
                }}
                onFilterCheck={onFilterCheck}
              />
            </div>
          </form>
        </div>
        <span className="flex items-center justify-between">
          <SearchSortBy
            className="md:hidden"
            onSortByChanged={onSortByChanged}
          />
          <Button
            className="!px-4 md:hidden"
            variant="none"
            type="button"
            onClick={() => {
              setMobileFiltersOpen(true);
            }}
          >
            <FilterIcon className="w-4 text-blue-600" />
          </Button>
        </span>
        <SearchResultsGrid
          listings={listings}
          noResultsListings={noResultsListings}
        />
      </section>
      {listings?.length > 0 && (
        <SearchPagination
          pageNumber={parseInt(Router?.query?.page.toString()) - 1}
          paginationData={paginationData}
          handlePageClick={handlePageClick}
        />
      )}
    </main>
  );
}

export default Search;

If I search something on my site, the console will look like this:

in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF

I tried using loading as a gate, where it will only search if it is equal to idle, however I change the the loading state at the end of the search back to idle, therefore it triggers a loop.

Does anybody have any experience in how I will handle this? Is adding the router query to a useEffect to fire the search the right way to go? Or should I just pass the search function to all the things that need it?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source