Home >Web Front-end >JS Tutorial >Taking React-TanStack-Query to the Next Level in Next.js

Taking React-TanStack-Query to the Next Level in Next.js

Linda Hamilton
Linda HamiltonOriginal
2025-01-22 20:40:10549browse

Taking React-TanStack-Query to the Next Level in Next.js

Recall our previous discussion about replacing the traditional fetch useState useEffect approach with React-TanStack-Query? If you've been using the fundamentals—setting up QueryProvider, writing basic queries, and handling mutations—you've likely already experienced the advantages. However, we've only just begun to explore its capabilities.

Let's delve deeper into advanced techniques that will significantly enhance your data fetching, transforming your application's performance from "good" to "exceptionally fast."

Building a Stronger Foundation

Our previous guide demonstrated a basic movie listing using React-TanStack-Query:

<code class="language-javascript">const { data: movies, error, isLoading } = useQuery(['movies'], fetchMovies);</code>

This is a great starting point, but what if we aim for Netflix-level responsiveness? Let's elevate our techniques.

Advanced Techniques for Superior Performance

1. Intelligent Prefetching (Achieving Netflix-like Speed)

Our initial movie list required users to wait after clicking. We can drastically improve this:

<code class="language-javascript">// components/MovieList.jsx
import { useQueryClient } from '@tanstack/react-query';

export default function MovieList() {
  const queryClient = useQueryClient();

  // Leveraging our existing fetchMovies function
  const prefetchMovie = async (movieId) => {
    await queryClient.prefetchQuery({
      queryKey: ['movie', movieId],
      queryFn: () => fetchMovieDetails(movieId),
      // Maintain freshness 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>
  );
}</code>

Now, as users hover over a movie, the details are pre-loaded—instantaneous access upon clicking! ✨

2. Enhancing Mutations (Remember those?)

Our initial article covered basic mutations. Let's optimize them with optimistic updates:

<code class="language-javascript">// hooks/useUpdateMovie.js
export function useUpdateMovie() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: updateMovie,
    // The key improvement
    onMutate: async (newMovie) => {
      // Halt ongoing refetches
      await queryClient.cancelQueries(['movie', newMovie.id]);

      // Store current state (for rollback if needed)
      const previousMovie = queryClient.getQueryData(['movie', newMovie.id]);

      // Immediate (optimistic) update
      queryClient.setQueryData(['movie', newMovie.id], newMovie);

      return { previousMovie };
    },
    // Error handling
    onError: (err, newMovie, context) => {
      queryClient.setQueryData(
        ['movie', newMovie.id],
        context.previousMovie
      );
    },
  });
}</code>

3. Parallel Data Loading (Eliminate Unnecessary Waiting)

Sequential loading is a thing of the past:

<code class="language-javascript">// 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 cast={cast} movie={movie} reviews={reviews} />;
}</code>

4. Implementing Infinite Scrolling (The Efficient Way)

Let's upgrade our paginated example to seamless infinite scrolling:

<code class="language-javascript">// 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 (
    <div>
      {data.pages.map((page) => (
        page.movies.map((movie) => (
          <MovieCard key={movie.id} movie={movie} />
        ))
      ))}

      <div ref={ref}>
        {isFetchingNextPage ? <LoadingSpinner /> : null}
      </div>
    </div>
  );
}</code>

5. Leveraging Next.js 14 Server Components

This is a feature unavailable in our first article: Next.js 14 server component integration:

<code class="language-javascript">const { data: movies, error, isLoading } = useQuery(['movies'], fetchMovies);</code>

Pro Tips (Lessons Learned)

  1. Consistent Query Keys: Refine our movie query keys:
<code class="language-javascript">// components/MovieList.jsx
import { useQueryClient } from '@tanstack/react-query';

export default function MovieList() {
  const queryClient = useQueryClient();

  // Leveraging our existing fetchMovies function
  const prefetchMovie = async (movieId) => {
    await queryClient.prefetchQuery({
      queryKey: ['movie', movieId],
      queryFn: () => fetchMovieDetails(movieId),
      // Maintain freshness 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>
  );
}</code>
  1. Intelligent Refetching: Improve our basic refetch on window focus:
<code class="language-javascript">// hooks/useUpdateMovie.js
export function useUpdateMovie() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: updateMovie,
    // The key improvement
    onMutate: async (newMovie) => {
      // Halt ongoing refetches
      await queryClient.cancelQueries(['movie', newMovie.id]);

      // Store current state (for rollback if needed)
      const previousMovie = queryClient.getQueryData(['movie', newMovie.id]);

      // Immediate (optimistic) update
      queryClient.setQueryData(['movie', newMovie.id], newMovie);

      return { previousMovie };
    },
    // Error handling
    onError: (err, newMovie, context) => {
      queryClient.setQueryData(
        ['movie', newMovie.id],
        context.previousMovie
      );
    },
  });
}</code>
  1. Robust Error Handling: Enhance our basic error handling:
<code class="language-javascript">// 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 cast={cast} movie={movie} reviews={reviews} />;
}</code>

Choosing the Right Technique

  • Basic Queries (from our first article): Ideal for simple data fetching.
  • Prefetching: Best when user actions are predictable.
  • Parallel Queries: Use when multiple independent data sets are needed.
  • Infinite Queries: Suitable for lengthy, scrollable lists.
  • Optimistic Updates: Employ for that instantaneous user experience.

Conclusion

We've significantly advanced from our initial setup! These enhancements are crucial for creating a truly exceptional user experience.

Remember, you don't need to implement everything at once. Start with the basics, then progressively incorporate these optimizations as needed.

The next time someone comments on your application's speed, you'll know exactly why it's so impressive. ?

Happy coding! React-TanStack-Query offers endless possibilities. What should we explore next?

The above is the detailed content of Taking React-TanStack-Query to the Next Level in Next.js. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn