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
useSWR Documentation - Vercel
useEffect Documentation - React