DEV Community

Cover image for From useEffect to TanStack Query: What Changed My Perspective
Mohammad Shahzeb Alam
Mohammad Shahzeb Alam

Posted on

From useEffect to TanStack Query: What Changed My Perspective

When I first started learning data fetching in React, the dependency array in useEffect confused me more than the fetch request itself.

But after building a Meme Generator, it finally started making sense.

A few weeks later, I built another project using TanStack Query because I wanted to see how a different approach to server state felt in practice.

I wasn't trying to prove that one approach was better than the other. I simply wanted to compare what it actually felt like to build with both.

What surprised me wasn't just that I wrote less code—it was how differently I started thinking about data fetching.


2. The Experiment

Project 1 — Meme Generator

My first project was a simple Meme Generator. The idea was straightforward: fetch a list of meme templates from an API, display a random meme, and let users add their own top and bottom text.

To fetch the meme data, I used React's useEffect hook. It was my first experience working with APIs in React, and it helped me understand how to fetch data after a component renders and store it in state.

useEffect(() => {
  fetch("https://api.imgflip.com/get_memes")
    .then((res) => res.json())
    .then((data) => setAllMemes(data.data.memes));
}, []);
Enter fullscreen mode Exit fullscreen mode

Besides data fetching, this project also introduced me to controlled components. As users typed into the input fields, the text updated on the meme image in real time, which was one of my favorite parts of building the project.


Project 2 — Movie Search App

After building my Meme Generator with useEffect, I wanted another project where I could compare a different approach to data fetching. This time, I built a Movie Search app using TanStack Query and React Router.

The app lets users search for movies, display the results, and navigate to a separate page for detailed information about each movie. It felt like a good project because it involved fetching data, searching, and working with dynamic routes—all things I'd likely use in real applications.

Instead of focusing on how TanStack Query worked internally, I wanted to see how it felt to build with it compared to useEffect.

const {
  data: movies,
  isLoading,
  isError,
} = useQuery({
  queryKey: ["movies", searchTerm],
  queryFn: fetchMovies,
});
Enter fullscreen mode Exit fullscreen mode

One thing I also wanted to explore was how TanStack Query worked alongside React Router. After searching for a movie, users can open a dedicated details page for that movie, making the app feel more like a real-world project.

At this point, I had built one project with useEffect and another with TanStack Query. The real differences didn't become obvious until I started comparing the development experience.


3. What Actually Surprised Me

Discovery #1 — Less Boilerplate, More Focus

The first thing I noticed was how much repetitive code disappeared.

With useEffect, I had to manage loading states, error states, and the fetched data myself. In my Movie Search app, TanStack Query handled most of that for me. Instead of writing the same boilerplate every time I fetched data, I could focus on building the feature.

const {
  data: movies,
  isLoading,
  isError,
} = useQuery({
  queryKey: ["movies", searchTerm],
  queryFn: fetchMovies,
});
Enter fullscreen mode Exit fullscreen mode

I didn't realize how much time I was spending writing the same patterns until I stopped having to write them.


Discovery #2 — Caching Finally Clicked

Caching was the feature that surprised me the most.

At first, I had heard people talk about caching, but I didn't really understand why it mattered.

While testing the app, I noticed that data I'd already fetched appeared instantly when I visited it again.

That was the moment caching finally clicked for me.

Instead of making another network request every time, TanStack Query reused the data it already had.

The difference wasn't dramatic in such a small project, but it helped me understand why caching becomes valuable as applications grow.


Discovery #3 — The Pattern Stayed Consistent

One thing I appreciated was that every data-fetching feature followed the same structure.

Whether I was fetching a list of movies or the details of a single movie, I used the same useQuery pattern:

  • define a queryKey
  • provide a queryFn
  • access data, isLoading, and isError

Once I understood that pattern, adding another API request felt much less intimidating.


4. What TanStack Query Didn't Replace

Before using TanStack Query, I expected it to completely change how I fetched data.

After building the project, I realized that wasn't the case.

I still needed:

  • An API to request data from.
  • A function that actually fetches the data (fetch in my project).
  • React fundamentals to build the UI.

For example, my fetchMovies function stayed almost the same:

const fetchMovies = async () => {
  const apiKey = import.meta.env.VITE_OMDB_API_KEY;

  const response = await fetch(
    `https://www.omdbapi.com/?apikey=${apiKey}&s=${searchTerm}`
  );

  const data = await response.json();

  return data.Search;
};
Enter fullscreen mode Exit fullscreen mode

TanStack Query doesn't replace this function. It simply calls it whenever the query needs fresh data.

What changed was how I managed the server state around that request.

Instead of writing repetitive code for loading states, error handling, caching, and refetching, I could describe my data requirements with useQuery, and TanStack Query handled those concerns for me.

For me, TanStack Query doesn't replace React or the Fetch API. It builds on top of them by removing repetitive server-state management, allowing me to spend more time building the application instead of managing data fetching logic.


5. When I'd Still Use useEffect

After building this project, I don't think TanStack Query is something I'd use in every React application.

If I'm building a small application with just one or two API requests, I'd probably stick with useEffect.

For those projects, installing and configuring another library doesn't feel necessary. useEffect already does the job well, and it keeps the project simple.

I'd start considering TanStack Query when an application grows and managing server state becomes repetitive. Features like caching, loading states, error handling, and automatic refetching become much more valuable as the project becomes more complex.

For me, it isn't about replacing useEffect. It's about choosing the right tool for the size and needs of the project.


6. Looking Back

Building these two projects changed how I think about data fetching in React.

If I rebuilt my Meme Generator today, I probably wouldn't replace useEffect with TanStack Query.

The app only fetches the list of memes once when it loads:

useEffect(() => {
  fetch("https://api.imgflip.com/get_memes")
    .then((res) => res.json())
    .then((data) => setAllMemes(data.data.memes));
}, []);
Enter fullscreen mode Exit fullscreen mode

For a simple project like this, useEffect is straightforward and gets the job done.

What has changed is how I'd approach larger applications.

If an app needs multiple API requests, caching, automatic refetching, or better server-state management, I'd seriously consider using TanStack Query.

Before building these two projects, I thought data fetching in React was mostly about making API requests.

Now I think it's more about deciding how to manage server state as an application grows.

This experiment didn't convince me that TanStack Query should replace useEffect.

Instead, it taught me that they solve different problems, and choosing the right one depends on what I'm building.

Top comments (0)