Next.js Discord

Discord Forum

Suspense not updating when search params get updated using next/navigation

I have this component A for filter on which I have added a suspense and fetching is done inside this component A. Inside this component I have used a checkbox which when checked replace the url using the next/navigation:
replace(`${pathName}?${params.toString()}`, { scroll: false });


When this url updates then again the fetching is done which should show the fallback component but it' s not showing the fallback component

26 Replies

I tried adding a key to the suspense like this(searchParams is the object providing the whole url slugs as well as query params:
<Suspense key={JSON.stringify(searchParams)} />


Still it's not working.
When I use replace to change the url on clickiing the checkbox its taking quite some time to update the url and after few seconds when the url updates, the data comes at that moment. Meaning , there is some delay to the next/navigation when updating the url.
Yes, whenever I hard refresh the page, the suspense is working but when updating the url through next/navigation (replace method) it's not working. When I click on the checkbox, it updates the url in 2-3 seconds (meaning there is a delay in updating the url) and once the url updates then the data comes within that time(no delay meaning within few milliseconds)
This is how I have used the component A which for this is CountryFilter :
{shouldShowCountries && (
              <Suspense fallback={<FilterItemSkeleton />}>
                <li>
                  <CountryFilter
                    key={JSON.stringify(searchParams)}
                    params={searchParams}
                  />
                </li>
              </Suspense>
            )}


And inside CountryFilter:
import {
  AggregateSearchFilter,
  fetchAggregateCountries,
} from "@/app/data/product-search";
import { ProductSearchParams } from "@/types/search";
import { FunctionComponent } from "react";
import CountryFilter from "./CountryList";

interface CountryFilterContainerProps {
  params: ProductSearchParams;
}
const CountryFilterContainer: FunctionComponent<
  CountryFilterContainerProps
> = async ({ params }) => {
  const countriesData: AggregateSearchFilter[] =
    await fetchAggregateCountries(params);

  return (
    <>
      {countriesData && countriesData.length !== 0 && (
        <CountryFilter countries={countriesData} />
      )}
    </>
  );
};

export default CountryFilterContainer;
it is indeed in the Suspense not in the CountryFilter component:
 <Suspense key={JSON.stringify(searchParams)} fallback={<FilterItemSkeleton />}>

Sorry, I copied the wrong code here... But yes, i have used the key on suspense like this:
{shouldShowCountries && (
              <Suspense
                key={JSON.stringify(searchParams)}
                fallback={<FilterItemSkeleton />}
              >
                <li>
                  <CountryFilter params={searchParams} />
                </li>
              </Suspense>
            )}
This issue is happening whenever I try to update the url in a client component using next/navigation like this in my checkbox component :
/* eslint-disable react-hooks/exhaustive-deps */
"use client";
import { AggregateSearchFilter } from "@/app/data/product-search";
import Checkbox from "@/components/commons/Checkbox";
import { FilterParams } from "@/constants/search";
import useSearchFilter from "@/hooks/useSearchFilter";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { ChangeEvent, FunctionComponent, useEffect, useState } from "react";

const CountryCheckbox: FunctionComponent<{
  country: AggregateSearchFilter;
}> = ({ country }) => {
  const searchParams = useSearchParams();
  const { replace } = useRouter();
  const pathName = usePathname();

  const isCountrySelected = searchParams
    .getAll(FilterParams.COUNTRIES)
    .includes(country.id.toString());

  const [isChecked, setIsChecked] = useState<boolean>(isCountrySelected);

  const { addToSelectedCountries, removeFromSelectedCountries } =
    useSearchFilter();

  useEffect(() => {
    if (isCountrySelected) {
      setIsChecked(true);
      addToSelectedCountries(country);
    } else {
      setIsChecked(false);
      removeFromSelectedCountries(country.id);
    }
  }, [country, isCountrySelected]);

  const handleSearch = (e: ChangeEvent<HTMLInputElement>) => {
    ... some code
    replace(`${pathName}?${params.toString()}`, { scroll: false });
  };

  return (
    <Checkbox
      value={country.id}
      label={country.name}
      onChange={handleSearch}
      checked={isChecked}
    />
  );
};

export default CountryCheckbox;
There is still an issue open on next.js repository on github:
https://github.com/vercel/next.js/issues/53543

This is what's happening I think
Nope, it's not working...When I refresh the page then the Loading shows but later when I click on the checkbox it's not working... I think this is the similar issue I am currently facing with:
https://github.com/vercel/next.js/issues/
I am on next@14.1.3 but this issue has been since few months for me...still no solution... First, even on reloading the page suspense didn't used to work which was the issue of not adding key as next doesn't include the searchParams in the key of a route so navigation ot the same route do not retrigger the suspense fallback
Now, this has been a major issue for me as fetching new data doesn't load any loaders or fallback component to show which is very bad for Ui/Ux
Yes, it sure does make a network request when clicking on the checkbox:
could you share the repo or create a repoduction?
Yes, I did check using the timeout of 2 seconds and indeed it's working and fallback component is showing but still there is some delay in showing the fallback component when I do like this :"
import { ProductSearchParams } from "@/types/search";
import { FunctionComponent } from "react";
import DepartureDateList from "./DepartureDateList";
import { fetchAggregateDepartureMonths } from "@/app/data";

const DestinationFilterContainer: FunctionComponent<{
  params: ProductSearchParams;
}> = async ({ params }) => {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  const { data: departureMonths } = await fetchAggregateDepartureMonths(params);

  return (
    <>
      {departureMonths && departureMonths.length !== 0 && (
        <DepartureDateList departureMonths={departureMonths} />
      )}
    </>
  );
};

export default DestinationFilterContainer;
This is how fetching is done :
export const fetchAggregateDepartureMonths = async (
  params: AggregateSearchParams,
) => {
  const url = buildProductSearchQueryParams({
    params,
    url: new URL(API_GET_PRODUCT_SEARCH_AGGREGATE_DEPARTURE_MONTHS),
  });

  Logger.info(`Fetching aggregate departure months ${url.toString()}`);

  const response = await fetch(url.toString(), { cache: "no-store" });

  if (!response.ok) {
    const error = new Error(response.statusText, { cause: response?.status });

    Logger.error(`Failed to fetch departure months`, error);

    throw error;
  }

  return response.json() satisfies Promise<AggregateSearchFilter[]>;
};


Also in the buildProductSearchQueryParams its just updating url as per the params which are available
Also, does this means we cannot use suspense on next/navigation or links ???: