DEV Community

Cover image for Get rid of useEffect for Data: Why React Query is a Game Changer
saijami
saijami

Posted on

Get rid of useEffect for Data: Why React Query is a Game Changer

This document explores the limitations of using useEffect and useState for data fetching in React applications and introduces React Query as a superior alternative. It highlights the benefits of React Query, such as automatic caching, refetching on focus, built-in loading/error states, and smart syncing, which significantly reduce boilerplate code and improve the overall development experience. The document aims to encourage developers to consider React Query for more efficient and maintainable data management in their React projects.

The useEffect + useState Struggle

For a long time, the go-to pattern for fetching data in React involved a combination of useEffect and useState. It looked something like this:

import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch('/api/data');
        const jsonData = await response.json();
        setData(jsonData);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []); // Empty dependency array for initial fetch

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  if (!data) return <p>No data to display</p>;

  return (
    <div>
      {/* Display your data here */}
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

While this approach works, it quickly becomes bulky as applications grow in complexity. Here's why:

  • Manual Loading and Error Handling: You're responsible for managing loading and error states manually, leading to repetitive code across components.
  • No Automatic Caching: Every time the component mounts (or the dependency array changes), a new API call is made, even if the data hasn't changed. This wastes bandwidth and can lead to a poor user experience.
  • Manual Refetching: If the data on the server changes, you need to implement a mechanism to manually refetch the data.
  • Stale Data: The data displayed might be stale, especially if the user switches between tabs or navigates away and back to the component.
  • Complex State Management: Managing multiple states (data, loading, error) can become challenging, especially in more complex scenarios.

React Query to the Rescue

React Query is a powerful library that simplifies data fetching, caching, and state management in React applications. It provides a declarative and efficient way to interact with APIs, reducing boilerplate code and improving the overall development experience.

Here's how the same data fetching logic looks with React Query:

import { useQuery } from 'react-query';

async function fetchData() {
  const response = await fetch('/api/data');
  return response.json();
}

function MyComponent() {
  const { data, isLoading, error } = useQuery('myData', fetchData);

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  if (!data) return <p>No data to display</p>;

  return (
    <div>
      {/* Display your data here */}
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

Notice how much cleaner and more concise the code is. React Query handles the loading, error, and data states internally, freeing you from managing them manually.

Key Benefits of React Query

Here's a breakdown of the key advantages React Query offers:

  • Automatic Caching: React Query automatically caches data in memory, so subsequent requests for the same data are served from the cache instead of making a network request. This significantly improves performance and reduces network traffic. The cache is intelligently managed, invalidating stale data based on configurable settings.
  • Refetch on Focus: React Query automatically refetches data when the user refocuses the browser window or tab. This ensures that the data displayed is always fresh and up-to-date. This behavior is configurable and can be disabled if needed.
  • Built-in Loading and Error States: React Query provides built-in isLoading and error states, eliminating the need to manage them manually. This simplifies the component logic and reduces boilerplate code.
  • Smart Syncing: React Query provides powerful mutation capabilities that allow you to update data on the server and automatically sync the changes across your application. This ensures data consistency and eliminates the need for manual state updates. Optimistic updates and rollback mechanisms are also supported.
  • Background Updates: React Query can automatically update data in the background, ensuring that the user always sees the most recent information without interrupting their workflow.
  • Pagination and Infinite Scrolling: React Query provides built-in support for pagination and infinite scrolling, making it easy to handle large datasets.
  • Devtools: React Query comes with a powerful devtools extension that allows you to inspect the cache, view query status, and trigger manual refetches. This makes debugging and troubleshooting much easier.

Conclusion

React Query is a game-changer for data fetching in React applications. It provides a declarative, efficient, and maintainable way to manage data, reducing boilerplate code and improving the overall development experience. If you're still using useEffect and useState for data fetching, I highly recommend giving React Query a try. You'll be amazed at how much simpler and more efficient your data management can be.

Top comments (0)