Skip to content
TanStack Query실무 경험

Menus Collapsing on Filter Change, Fixed with TanStack Query placeholderData

2 min readWorkMoved from my Tistory blogRead the Korean original

This is a write-up of a refactor that started with a UX problem in the left Accordion menu of the Autowini filter search page, where I used TanStack Query's placeholderData option to improve the structure.

The problem

The left side of the Autowini filter search page is built as an Accordion UI, and it was implemented to fetch the filter data from the server again every time the filter conditions changed.

This caused the following UX problem.

  • Filter parameters change
  • The API is called again
  • data is undefined while loading
  • The lists inside the Accordion become empty
  • Every menu collapses and then expands again

For users, it felt like the screen flickered or the filters reset every time they changed a filter.

My first fix

To fix the UX problem quickly, I first used React's useState to keep the previous filter data.

The core idea was simple: "Keep showing the previous data while new data is loading."

ts
/**
 * @description Hook that fetches filter search data
 */
export const useFilterSearch = ({
  itemType,
  filterSearchParams,
}: {
  itemType: string;
  filterSearchParams: FilterSearchParams;
}) => {
  const [filterData, setFilterData] = useState<FilterResponse['data'] | null>(
    null
  );

  const { data, isLoading, isError } = useQuery({
    queryKey: ['useFilterSearch', itemType, filterSearchParams],
    queryFn: () =>
      apiClient.get<FilterResponse>(`${API_URL}`, {
        params: filterSearchParams,
      }),
  });

  // Keep the Accordion menu from looking empty while loading
  useEffect(() => {
    if (data?.data?.data && !isError) {
      setFilterData(data.data.data);
    }
  }, [data, isError]);

  return {
    filterData,
    filterDataIsLoading: isLoading,
    filterDataIsError: isError,
  };
};

This fixed the collapsing Accordion menu, and the user experience improved right away.

Problems and the fix

In that implementation, I was using useState to manage the previous filter data separately in order to fix the UX problem. But this approach had a few structural limitations.

  • React Query was already managing the cache
  • The same data was managed a second time with useState
  • Unnecessary memory use
  • More code complexity from the added useEffect

In other words, I was managing the state twice even though I was using React Query, and I started wondering whether this logic could be made simpler.

What I used to solve this was TanStack Query's placeholderData option. placeholderData is an option that helps you keep the previous data as is even while new data is being fetched after the query key changes.

With this option, data does not become undefined during loading, the data inside the Accordion stays in place, and the menu state is preserved naturally.

As a result, I could hand off to React Query the logic I had been handling myself with useState and useEffect, and along with the UX improvement, the code structure became much simpler.

ts
/**
 * @description Hook that fetches filter search data
 */
export const useFilterSearch = ({
  itemType,
  filterSearchParams,
}: {
  itemType: string;
  filterSearchParams: FilterSearchParams;
}) => {
  const { data, isLoading, isError } = useQuery({
    queryKey: ['useFilterSearch', itemType, filterSearchParams],
    queryFn: () =>
      apiClient.get<FilterResponse>(`${API_URL}`, {
        params: filterSearchParams,
      }),
    // Keep the previous data as is until the new data has loaded
    placeholderData: (previousData) => previousData,
  });

  return {
    filterData: data?.data?.data,
    filterDataIsLoading: isLoading,
    filterDataIsError: isError,
  };
};

Results and takeaways

With this change, removing the useState and useEffect I had been using simplified the logic flow, and cleaning up the unnecessary structure that managed state twice also brought an improvement in memory use.

I thought I had been using TanStack Query fairly well, but having missed an option like placeholderData that I could use to fit the situation made me realize there is still a lot more for me to study.

  • #placeholderData
  • #TanStack Query
  • #useEffect
  • #useQuery
  • #useState
  • #Fixing menus that collapse and reopen
  • #Frontend Developer

More in Work