Nextjs 13: How to show the loading ui when search params changes?

1.9k views Asked by At

I have this component here:


const MyComponent = (props: Props) => {
  const router = useRouter();
  const searchParams = new URLSearchParams(
    Array.from(useSearchParams().entries())
  );

  const form = useForm({
    defaultValues: {
      category: searchParams.get("category") || NaN,
      subcategory: searchParams.get("subcategory") || NaN,
    },
  });

  const subcategory = form.watch("subcategory");

  const updateParams = (
    category: string | number,
    subcategory: string | number
  ) => {
    searchParams.set("category", String(category));
    searchParams.set("subcategory", String(subcategory));

    router.push(`/my-url/?${searchParams.toString()}`);
  };

  useEffect(() => {
    if (subcategory) {
      updateParams(category, subcategory);
    }
  }, [subcategory]);

  const category = form.watch("category"); 

That updates the search params based on user input (from checkout buttons). This triggers page refetch to update the data from UI. But, for some reason, it doesn't trigger the loading.tsx from this route.

I saw people reporting the same error at this github issue. Is there any workaround to make searchparams trigger a loading screen?

I know it would work with path param, but I need a URL with search parameters.

1

There are 1 answers

1
Oleksii Kotvytskyi On

I found Related issue on GitHub, but for me fix with the "unique key" for <Suspense /> component didn't help. So I solved it with the next approach.

  1. Rewrite from server component to client one.
  2. Fetch data in the useEffect hook and set loading as a state for the component.
  3. Set the skeleton inside of the component + in the fallback of <Suspense /> (as I understood the second one need for fixing the hydration error)

Code example:

  const [data, setData] = useState<Product[] | undefined>(undefined);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    setIsLoading(true);
    getData({categoryId, colorId, sizeId})
      .then((res) => {
        setData(res);
      })
      .finally(() => {
        setIsLoading(false);
      });
  }, [categoryId, colorId, sizeId]);

if (isLoading) return <ProductListLoading />;

return (
  <div>
    {data?.map((item) => ( // your logic here ))}
  </div>
);

Example with loading inside component and Suspense with fallback

Hope it will be helpful.