React's useEffect vs. useSWR: Exploring Data Fetching in React.

Emmanuel OlokeEmmanuel Oloke
9 min read

Introduction

The concept of data fetching in React applications is of high importance as it is often necessary to fetch data from an external source, such as an API or a database, and use that data to render components. React provides several ways to fetch data, including the built-in fetch method and popular third-party library Axios. One popular approach to data fetching in React is to use hooks like useEffect and useSWR from the swr third-party npm package. These hooks allow developers to fetch data and manage its state within a component, making it easy to update the UI in response to changes in the data.

By choosing the right approach to data fetching and using the appropriate tools, developers can create performant and responsive applications that provide a great user experience.

In this article, we are going to explore fetching data with React's useEffect hook, its main purpose and benefits, its drawbacks, and why you may want to use the useSWR hook for your data fetching moving forward in your React app.

Prerequisites

As always, before we proceed, it is important to note that knowledge of React and a bit of TypeScript is required to fully grasp the concepts discussed in this article.

Now let's get into it.

The Almighty useEffect

The useEffect hook was introduced in React version 16.8, released in February 2019. It gave the ability to perform side effects on functional components. Side effects are actions you want to perform when your component mounts, updates, or unmounts. This was to mimic the componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods in class components, and it allows developers to manage side effects, such as fetching data, updating the DOM, and subscribing to events within functional components. The introduction of useEffect was a major milestone for React, as it made it easier for developers to write functional components that could perform the same tasks as class components without sacrificing performance or readability.

Since its introduction, useEffect has become one of the most widely used hooks in the React ecosystem, and it has been instrumental in making React more accessible to developers of all skill levels.

In the useEffect section of this article, I explained with code samples how we could use the useEffect hook to replicate lifecycle methods of class components, do check it out.

Using useEffect for data fetching has its advantages and disadvantages depending on the scope of the project you're working on. For example, if you need to fetch data from a certain API endpoint and repeat that action when state variable changes in our component, then the useEffect hook is perfect for this kind of scenario. However, some of the drawbacks to this approach include the fact that useEffect does not provide additional features for handling things like loading and error state management.

To demonstrate and better explain this, we will build a simple react application bootstrapped with Vite and TypeScript and use Random User Generator API as our endpoint to get some data.

Enough talk. Let's get into the code.

To start with, we scaffold the project by navigating into our desired directory and running the following command:

npm create vite@latest

Running this command will give us some prompts, and you can provide them with the following options:

  • Project Name: swr-datafetching

  • Select a Framework: React

  • Select a variant: TypeScript

This will run for some time, and after, we can then run the following command:

cd swr-datafetching

npm install

npm run dev

We can then check the browser to see that the development server is running fine and open up the directory in a preferred code editor, in my case VSCode.

To start with, since this is a TypeScript project and we know the structure of the data the random user API will return to us, we first have to declare the types that we'll be using, we will not be using all the data returned by the API in this project, but it's advisable to type them anyway do to avoid being yelled at by the TypeScript compiler.

To do that, in the src folder, create a new file, and name it types.ts then add the following code in there:

export type ResultsProperties = {
  gender: string;
  name: Name;
  location: Location;
  email: string;
  login: Login;
  dob: DobOrRegistered;
  registered: DobOrRegistered;
  phone: string;
  cell: string;
  id: Id;
  picture: Picture;
  nat: string;
};
export type Name = {
  title: string;
  first: string;
  last: string;
};
export type Location = {
  street: Street;
  city: string;
  state: string;
  country: string;
  postcode: number | string;
  coordinates: Coordinates;
  timezone: Timezone;
};
export type Street = {
  number: number;
  name: string;
};
export type Coordinates = {
  latitude: string;
  longitude: string;
};
export type Timezone = {
  offset: string;
  description: string;
};
export type Login = {
  uuid: string;
  username: string;
  password: string;
  salt: string;
  md5: string;
  sha1: string;
  sha256: string;
};
export type DobOrRegistered = {
  date: string;
  age: number;
};
export type Id = {
  name: string;
  value?: string | null;
};
export type Picture = {
  large: string;
  medium: string;
  thumbnail: string;
};
export type Info = {
  seed: string;
  results: number;
  page: number;
  version: string;
};

The API returns an object with results and info properties which also contain other properties and objects, so what we did here was to properly type those properties to keep the TypeScript compiler happy.

Now inside src/App.tsx file, replace the content with the following code:

import { useEffect, useState } from 'react';
import './App.css';

import { ResultsProperties } from './types';

function App() {
  const [user, setUser] = useState<ResultsProperties | null>(null);
  const apiUrl = 'https://randomuser.me/api/';

  const fetcher = async (url: string) => {
    const response = await fetch(url);
    const data = await response.json();
    setUser(data.results[0] as ResultsProperties);
  };

  useEffect(() => {
    fetcher(apiUrl);
  }, []);

  const avatar = user?.picture;
  const largeAvatar = avatar?.large;

  const name = user?.name;
  const fullName = `${name?.title} ${name?.first} ${name?.last}`;

  return (
    <div className="App">
      {user === null ? (
        <h3>Loading...</h3>
      ) : (
        <>
          <img src={largeAvatar} alt="medium sized avatar image" style={{ borderRadius: '50%' }} />
          <h3>{fullName}</h3>
        </>
      )}
    </div>
  );
}

export default App;

In the above code, the aim is to render the user avatar and name data returned from the random user API to the screen. To achieve that, we start by importing useEffect and useState hooks from react and also the necessary styling from ./App.css. We then created a state variable called user initialized to null, and also initialized apiUrl variable with the appropriate API endpoint.

After that, we declared a fetcher function, which takes in a url parameter of type string, we used async/await and the fetch method to fetch data from the provided endpoint, parse the response as json() to the data variable and set the user state variable to the data.results[0] property as type ResultsProperties.

Then we call the useEffect hook, pass our fetcher function into the callback and pass an empty array of dependencies as the second argument to indicate that we want this effect to only run once after each render.

With this, we get access to the user object and use it to determine what the component renders. Note that in the returned JSX element, we had to manually implement the loading state by checking if user === null and conditionally rendering a different JSX element while the user data is being loaded. This is one of the drawbacks of useEffect as mentioned earlier in the article.

The other drawback has to do with the React.Strictmode. this only runs in development and does not affect production build. In previous versions, the Strictmode only mounts a component once, but from React 18, it does that twice, which can be confusing if not fully understood. Basically, with React.Strictmode our components gets mounted, unmounted, and mounted again, this can make it seem as if our component is being mounted twice in development, which as a result, in our case, will make our API call twice, as seen below.

If we look in the browser console, we can see that the API call was logged twice. To fix this, we can simply remove the React.Strictmode tag wrapping out App component in src/main.tsx file, or provide a clean-up function to our useEffect hook.

This all seems quite unnecessary for the scope of the application we're trying to build. What if there were a better way with additional perks even? Enter useSWR!

useSWR

useSWR is a custom hook provided by the swr library from Vercel that helps handle data fetching and caching in React components. It uses a technique called "Stale-While-Revalidate" (SWR), where it returns the cached data immediately and then fetches updated data in the background. This can help improve the perceived performance of an app by reducing the number of loading spinners and network requests.

To implement useSWR in our project, we first install the swr package by running the following command:

npm install swr

Then replace the code in our src/App.tsx with the following:

import useSWR from 'swr';
import './App.css';

import { ResultsProperties } from './types';

function App() {
  const apiUrl = 'https://randomuser.me/api/';

  const fetcher = async (url: string) => {
    const response = await fetch(url);
    const data = await response.json();
    return data.results[0];
  };

  const { data: user, isLoading, error } = useSWR<ResultsProperties>(apiUrl, fetcher);

  const avatar = user?.picture;
  const largeAvatar = avatar?.large;

  const name = user?.name;
  const fullName = `${name?.title} ${name?.first} ${name?.last}`;

  return (
    <div className="App">
      {isLoading ? (
        <h3>Loading...</h3>
      ) : error ? (
        <h3>An error occured</h3>
      ) : (
        <>
          <img src={largeAvatar} alt="medium sized avatar image" style={{ borderRadius: '50%' }} />
          <h3>{fullName}</h3>
        </>
      )}
    </div>
  );
}

export default App;

In the above code, we've removed the useEffect and useState hooks and replaced them with useSWR imported from swr instead, also, we are now returning data.results[0] as opposed to setting it to user variable with useState. Most of the implementation stays the same, but now with the useSWR method, all we need to do is call it and pass it our apiURL and the fetcher function as arguments. With that, we can then destructure out the data returned by the fetcher function and name it user. Also, the useSWR method provides us with two important functionalities for better state management of our API request, the isLoading variable (boolean) and the error (object). With this, we can better manage our API requests without having to manually implement things ourselves.

Now if we look in the browser console, we see that the API request is now only made once, even in Strictmode

Another advantage of useSWR is DEDUPING, which is basically a process of removing identical entries from two or more data sets. What this means is that if, for whatever reason, we are making API calls to the same endpoint in different places in our application, useSWR has the ability to dedupe that request and make it once instead, then reuse it in other identical places, thereby reducing the number of network requests required.

Other advantages include:

  • Avoids unnecessary re-render

  • No unnecessary requests

  • Avoid unnecessary code imports

Conclusion

To be honest, I will personally use useSWR more going forward, just because it offers more advantages in my opinion. However, useEffect has functionalities it was designed for originally, such as the life-cycle components implementation we discussed earlier in the article, amongst others, so if a project you are building requires you to do something similar to that, it will be a great option.

I hope you've found this piece helpful in one way or another. I had fun and learned more researching the topic and writing the article.

You can check out the finished code of the project built in this article here

Until I see you in the next one. Cheers ๐Ÿฅ‚โœŒ๐Ÿฝ

Reference Resources

7
Subscribe to my newsletter

Read articles from Emmanuel Oloke directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Emmanuel Oloke
Emmanuel Oloke

I am a developer from Nigeria passionate about building impactful tech products.