Don't fetch data with useEffect, use this instead ๐Ÿ‘‡

Don't fetch data with useEffect, use this instead ๐Ÿ‘‡

ยท

4 min read

Why I gave up on useEffect ?

Don't use useEffect for every damn state update!
It'll slow your app down and cause unnecessary re-renders. And if you forget to clean up after yourself or you'll end up with memory leaks that'll ruin your app's performance. Oh, and don't even get me started on using async functions inside useEffect โ€“ that'll just lead to a mess of unpredictable behavior and race conditions. Stick to async/await or Promise and save yourself the headache.

Performance issues

Improper use of useEffect hook can lead to performance issues. If you have too many useEffect hooks, it can result in too many unnecessary re-renders of your component, especially if you're not careful with the dependencies array. This can negatively impact the performance of your application.

Unforeseen side effects

If you're not careful, it's possible to cause unintended side effects with useEffect hooks. If the function passed to useEffect perform a state update that triggers another useEffect hook, it can cause a chain reaction that's difficult to debug.

Complexity

The useEffect hook can make the code more complex, especially when dealing with complex state management. It makes it harder to reason about the state of your application, especially if you have multiple useEffect hooks with complex dependencies.

Here's an example of how multiple useEffect hooks with complex state management can make the code more complex. In this example, we have two useEffect hooks that manage state updates and API calls. It can get complex if we need to add more state and more useEffect hooks to handle it. Care needs to be taken to ensure that the code stays readable and maintainable.

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

function App() {
  const [count, setCount] = useState(0);
  const [isLoading, setLoading] = useState(false);
  const [data, setData] = useState([]);

  useEffect(() => {
    setLoading(true);
    fetch(`https://jsonplaceholder.typicode.com/posts?userId=${count}`)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, [count]);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount(count => count + 1);
    }, 5000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div>
      {isLoading ? <p>Loading...</p> :
        <ul>
          {data.map(item => (
            <li key={item.id}>{item.title}</li>
          ))}
        </ul>
      }
    </div>
  );
}

Much better SWR

SWR stands for "Stale-While-Revalidate" and is a data-fetching library for React applications. It provides a simple and efficient way to fetch and cache data from APIs, with built-in support for features like pagination, revalidation, mutation, deduping, caching, and error handling. I stopped using useEffect, and feel like everyone building a medium size web app, should too !

Understanding SWR logic

After an API call, it returns a cached value first to make the UI render instantly, while it also revalidates the data (fetches the latest data and compares the difference) and updates the state if there is any change. In that manner, there's a lot of improved performance as we are getting the data quickly from the cache and not calling the API again, and a much quicker and optimistic UI-based response.

If we update any data, we can easily call the mutate function which will update the cached data.

Super clean code

Here is an example code snippet which btw looks much cleaner than useEffect that shows how to use SWR to fetch data from an API:

import useSWR from 'swr';

function MyComponent() {
  const { data, error } = useSWR('https://jsonplaceholder.typicode.com/users');

  if (error) return <div>Error loading data</div>; // in-built error checking
  if (!data) return <div>Loading...</div>; // in-built loading

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {data.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default MyComponent;

How is SWR better ?

Simplified code

In contrast to useEffect and state management, SWR simplifies data fetching code by providing a single hook that handles fetching, caching, and state management. This results in less code overall, making it easier to read, debug, and maintain.

Caching and revalidation

SWR improves React data fetching performance with built-in caching and revalidation for frequently changing or large datasets. It updates cached data automatically and reduces network calls by allowing you to set revalidation frequency. This helps in faster response, but fewer API calls.

Deduplication

If multiple components are trying to fetch the same data at the same time, SWR will make sure that only one request is sent to the server and subsequent requests are deduplicated. SWR achieves this by caching the response of the first request and using that response for subsequent requests. If you call the same API 10 times, it'll be called just 1 time.

function Avatar () {
  const { data, error } = useUser()

  if (error) return <Error />
  if (!data) return <Spinner />

  return <img src={data.avatar_url} />
}

// useUser()'s API will be called just once.
function App () {
  return <>
    <Avatar />
    <Avatar />
    <Avatar />
    <Avatar />
    <Avatar />
  </>
}

Conclusion

SWR is the React library that stands out in simplifying data fetching, elevating performance with caching and revalidation, and eliminating the hassle of redundant requests. It takes complex state management to the next level, surpassing the traditional useEffect hook in terms of efficiency.

DROP A FOLLOW ๐Ÿ‘‡๐Ÿ‘‡๐Ÿ‘‡

Did you find this article valuable?

Support Tamal Das by becoming a sponsor. Any amount is appreciated!

ย