Remember our last chat about ditching the old fetch + useState + useEffect
combo for React-TanStack-Query? If you've been using those basics—setting up the QueryProvider, writing simple queries, and handling mutations—you're probably already seeing the benefits. But here's the thing: we've only scratched the surface.
Let's dive deeper and explore some powerful techniques that'll take your data fetching game from "pretty good" to "how did you make it so fast?"
Building on Our Foundation
In our previous guide, we set up a basic movie listing with React-TanStack-Query:
const { data: movies, error, isLoading } = useQuery(['movies'], fetchMovies);
That's great for getting started, but what if we want Netflix-like performance? You know, where everything feels instant? Let's upgrade our toolkit.
Advanced Techniques That Feel Like Magic
1. Smart Prefetching (Or: Why Netflix Feels So Fast)
Remember how our movie list worked before? Click and wait. But we can do better. Much better:
// components/MovieList.jsx
import { useQueryClient } from '@tanstack/react-query';
export default function MovieList() {
const queryClient = useQueryClient();
// Building on our previous fetchMovies function
const prefetchMovie = async (movieId) => {
await queryClient.prefetchQuery({
queryKey: ['movie', movieId],
queryFn: () => fetchMovieDetails(movieId),
// Keep it fresh for 5 minutes
staleTime: 5 * 60 * 1000,
});
};
return (
<div className="grid grid-cols-4 gap-4">
{movies.map(movie => (
<div
key={movie.id}
onMouseEnter={() => prefetchMovie(movie.id)}
className="movie-card"
>
{movie.title}
</div>
))}
</div>
);
}
Now when users hover over a movie, we're secretly loading the details before they click. Magic! ✨
2. Upgrading Our Mutations (Remember those?)
In our first article, we looked at basic mutations. But let's make them feel instant with optimistic updates:
// hooks/useUpdateMovie.js
export function useUpdateMovie() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateMovie,
// Here's where the magic happens
onMutate: async (newMovie) => {
// Pause outgoing refetches
await queryClient.cancelQueries(['movie', newMovie.id]);
// Save current state (in case we need to roll back)
const previousMovie = queryClient.getQueryData(['movie', newMovie.id]);
// Update immediately (optimistically)
queryClient.setQueryData(['movie', newMovie.id], newMovie);
return { previousMovie };
},
// Uh oh, something went wrong
onError: (err, newMovie, context) => {
queryClient.setQueryData(
['movie', newMovie.id],
context.previousMovie
);
},
});
}
3. Parallel Loading (Because Why Wait?)
Remember loading one thing at a time? Those days are over:
// pages/movie/[id].js
export default function MoviePage({ movieId }) {
const results = useQueries({
queries: [
{
queryKey: ['movie', movieId],
queryFn: () => fetchMovie(movieId),
},
{
queryKey: ['cast', movieId],
queryFn: () => fetchCast(movieId),
},
{
queryKey: ['reviews', movieId],
queryFn: () => fetchReviews(movieId),
},
],
});
if (results.some(result => result.isLoading)) {
return <LoadingSpinner />;
}
const [movie, cast, reviews] = results.map(r => r.data);
return <MovieDetails movie={movie} cast={cast} reviews={reviews} />;
}
4. Infinite Scrolling (The Right Way)
Remember our paginated example? Let's upgrade it to smooth infinite scrolling:
// components/InfiniteMovieList.jsx
import { useInfiniteQuery } from '@tanstack/react-query';
import { useInView } from 'react-intersection-observer';
export default function InfiniteMovieList() {
const { ref, inView } = useInView();
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['movies'],
queryFn: fetchMoviePage,
getNextPageParam: (lastPage) => lastPage.nextCursor,
});
useEffect(() => {
if (inView && hasNextPage) {
fetchNextPage();
}
}, [inView, hasNextPage]);
return (
<>
{data.pages.map((page) => (
page.movies.map((movie) => (
<MovieCard key={movie.id} movie={movie} />
))
))}
<div ref={ref}>
{isFetchingNextPage ? <LoadingSpinner /> : null}
</div>
</>
);
}
5. Next.js 14 Server Component Magic
Now here's something we couldn't do in our first article—Next.js 14 server component integration:
// app/movies/page.js
import { Hydrate, dehydrate } from '@tanstack/react-query';
import { getQueryClient } from '@/utils/getQueryClient';
export default async function MoviesPage() {
const queryClient = getQueryClient();
// Load data during SSR
await queryClient.prefetchQuery({
queryKey: ['movies'],
queryFn: fetchMovies,
});
const dehydratedState = dehydrate(queryClient);
return (
<Hydrate state={dehydratedState}>
<MovieList />
</Hydrate>
);
}
Pro Tips (Things I Wish I Knew Earlier)
- Consistent Query Keys: Build on our previous movie query keys:
// Instead of just ['movies']
['movies', { genre: 'action', year: 2024 }]
- Smart Refetching: Remember our basic refetch on window focus? Let's make it smarter:
useQuery({
queryKey: ['movies'],
queryFn: fetchMovies,
refetchOnWindowFocus: process.env.NODE_ENV === 'production',
staleTime: 5 * 60 * 1000, // 5 minutes
});
- Error Recovery: Building on our basic error handling:
<ErrorBoundary
fallback={({ error, resetError }) => (
<div>
<p>Something went wrong: {error.message}</p>
<button onClick={resetError}>Try again</button>
</div>
)}
>
<MovieList />
</ErrorBoundary>
When to Use What
- Basic Queries (from our first article): For simple, straightforward data fetching
- Prefetching: When you can predict the user's next move
- Parallel Queries: When you need multiple pieces of independent data
- Infinite Queries: For long, scrollable lists
- Optimistic Updates: When you want that instant feel
Wrapping Up
We've come a long way from our basic setup in the first article! These advanced techniques aren't just fancy additions—they're the difference between an app that works and an app that wows.
Remember: you don't need to implement everything at once. Start with the basics we covered in the first article, then gradually add these optimizations where they make sense for your app.
Next time someone asks you "Why does your app feel so fast?", you'll know exactly why. 😎
Happy coding! And remember, there's always more to learn with React-TanStack-Query. What should we explore next?
Top comments (0)