How we supercharged our Auction flows with TanStack query
Efficient state management is crucial when building a React Native app. Managing auctions, documentation, and other operations required careful handling of server and UI state. Initially, we relied entirely on Redux, but as the flow grew, challenges started piling up.
That’s when we brought in TanStack Query for optimizations. Let’s explore the problems we faced and how TanStack Query helped us overcome them.
The Problems
- Slow Navigation:
- We fetched all the required data upfront and stored it in Redux. This blocked navigation until all data was loaded, causing noticeable delays on slower networks.
- Boilerplate Overload:
- Managing loading, error, and refetching required extensive boilerplate code with actions, reducers, and dispatch logic.
- Caching Challenges:
- Storing long-term cache for static content (e.g. CMS data) alongside dynamic data refresh for user actions was cumbersome with Redux.
The Solution: Why TanStack Query
We introduced TanStack Query, which allowed us to offload server state (data coming from the backend) management from Redux while maintaining UI state (e.g., when increasing/decreasing price or state of the auction) in Redux. This hybrid approach reduced complexity and significantly improved performance. Here’s what we did:
1. Leveraging Query Keys for Modular State Management
To make query management easier, we structured all query keys with a common prefix (auction). This allowed us to manage all queries related to auctions as a group efficiently.
useQuery({ queryKey: ['auction', 'details', id], queryFn: () => getAuctionDetails(id), staleTime: Infinity, gcTime: 5 * 60 * 1000, });
2. No boilerplate code required
We no longer needed Redux boilerplate for managing loading and error states. The library’s built-in variables handled it gracefully:isLoading ,isError ,refetch
const AuctionDetailsScreen = ({ id }) => { const { data, isLoading, isError, refetch } = useQuery({ queryKey: ['auction', 'details', id], queryFn: () => getAuctionDetails(id), staleTime: Infinity }); if (isLoading) return <LoadingSpinner />; if (isError) return <ErrorScreen onRetry={refetch} />; return <AuctionDetails data={data} />; };
3. Avoiding Prop Drilling
TanStack Query allowed us to reuse the same query hook in child components without needing to prop drill data from parent components. If the staleTime is long enough, the data is fetched from the cache instead of making another API call.
import { useQuery } from '@tanstack/react-query'; const useAuctionDetails = (id) => { return useQuery({ queryKey: ['auction', 'details', id], queryFn: () => fetchAuctionDetails(id), staleTime: 60 * 60 * 1000, }); }; const ParentComponent = ({ id }) => { const { data, isLoading } = useAuctionDetails(id); if (isLoading) return <LoadingSpinner />; return ( <div> <h1>{data.title}</h1> <ChildComponent id={id} /> </div> ); }; const ChildComponent = memo(({ id }) => { const { data } = useAuctionDetails(id); // Reuses the same cached data return <p>{data.description}</p>; });
4. Efficient Cache Management with staleTime and gcTime
TanStack Query allowed us to control data freshness and memory usage effectively:
- Static Content (e.g., CMS Data):
useQuery({ queryKey: ['auction', 'cms-data'], queryFn: fetchCMSData, staleTime: Infinity, gcTime: 24 * 60 * 60 * 1000, // Cache for 24 hours });
- Dynamic Content (e.g., Auction Details):
useQuery({ queryKey: ['auction', 'details', id], queryFn: () => getAuctionDetails(id), staleTime: Infinity gcTime: 5 * 60 * 1000, // Cache for 5 minutes });
5. Advanced Query Functions
TanStack Query provides powerful utility functions to manage query state. Here’s how we used them:
Prefetch Queries
To preload data before the user navigates to a screen:
queryClient.prefetchQuery({ queryKey: ['auction', 'details', id], queryFn: () => getAuctionDetails(id), });
Get Query Data
Retrieve the cached state of a query:
const data = queryClient.getQueryData(['auction', 'details', id]); console.log('Cached data:', data);
Fetch Fresh Data
Forcing a fresh fetch requires a combination of invalidateQueries and getQueryData:
await queryClient.invalidateQueries({ queryKey: ['auction', 'details', id] }); const freshData = queryClient.getQueryData(['auction', 'details', id])?.data;
Clearing cache after flow completion
To refresh all queries that start with auction:
queryClient.resetQueries({ queryKey: ['auction'], exact: false, // Clear all queries starting with 'auction' });
If exact is false , all queries starting with auction are reset.
6. Debugging with React Query DevTools
The react-native-react-query-devtools plugin made it easy to debug queries by visualizing their state, cache, and fetch cycles.
Conclusion
Switching to TanStack Query transformed how we handle server state, making our app faster and easier to maintain. Thanks for your time.
Loved this article?
Hit the like button
Share this article
Spread the knowledge
More from the world of CARS24
Refactoring Auth, Not Breaking Prod: A Case Study in S2S Migration
We needed a control centre — something that unified authentication, gave us visibility, and offered extensibility. So we leaned into something we already trusted internally: UMS, our Keycloak-based authentication platform.
Navigating AWS Service Migration: Challenges and Lessons Learned
A recent audit compliance reasons forced us to migrate NBFC tech to a separate account, ensuring clear data ownership, proper segregation, and controlled data sharing with other entities.
Chaos to Clarity: How We Built a Scalable Observability System for Logs, Metrics and Traces
This blog focuses on how we built our Observability Stack at CARS24, which includes Monitoring, Logging, and Tracing, to gain insights about our deployed services and receive instant alerts when something unexpected happens.