React is the most used front end library for building modern, interactive front end web apps. It can also be used to build mobile apps.
In this article, we’ll look at how to load data in our React components.
Loading Data When the Component First Loads with the useEffect Hook
If we’re using hooks in React function components, we can load data from an API when the component first loads by using the useEffect
hook with an empty array as the second argument.
For instance, we can load data by writing:
import React, { useEffect, useState } from "react";
export default function App() {
const [data, setData] = useState({});
const loadData = async () => {
const res = await fetch("https://api.agify.io/?name=michael");
setData(await res.json());
};
useEffect(() => {
loadData();
return () => {};
}, []);
return <p>{data.name}</p>;
}
In the code above, we use the useEffect
hook to fetch data from an API and then set the result to the data
state variable with the setData
function.
Then we call the loadData
function in the useEffect
callback. The 2nd argument of the useEffect
hook is an empty array.
React then will rerender the data as the data from the API is fetched. The useEffect
callback will only run when the component first loads if the 2nd argument of useEffect
is an empty array.
Loading Data When the Component’s Prop Changes
If we pass in the prop variable to the array in the 2nd argument of useEffect
, the callback that we pass into the 1st argument will run as the prop changes.
For instance, we can write the following code fetch data from an API as our prop changes:
import React, { useEffect, useState } from "react";
const Name = ({ name }) => {
const [data, setData] = useState({});
const loadData = async () => {
const res = await fetch(`https://api.agify.io/?name=${name}`);
setData(await res.json());
};
useEffect(() => {
loadData();
return () => {};
}, [name]);
return <p>{data.name}</p>;
};
export default function App() {
const [name, setName] = useState("");
return (
<div>
<input onChange={e => setName(e.target.value)} />
<Name name={name} />
</div>
);
}
In the code above, we have an input in App
, where the inputted value is set as the value of the name
variable with setName
. Then we have the Name
component, which passes the name
prop with the value name
,
In Name
, we have the useEffect
hook which is similar to what we have before. The name
prop is passed into the array in the second argument of useEffect
, which means that it’s watched.
Note that we returned a function in the useEffect
callback. This is used for clean up code. Whenever we have something in the array in the 2nd argument of useEffect
, we have to return a cleanup function. Otherwise, we’ll get a ‘destroy is not a function’ error.
Then when we type in something, we’ll see that data is fetched from the API and the data is displayed as the output.
Adding a Loading Indicator in Fetch Calls
We can use the react-promise-tracker
package to watch for loading promises and sets a state to indicate that one or more promises are loading.
To use it, we first install it by running:
npm i react-promise-tracker
Then we can use it as follows:
import React, { useEffect, useState } from "react";
import { usePromiseTracker, trackPromise } from "react-promise-tracker";
const sleep = ms => new Promise(r => setTimeout(r, ms));
export default function App() {
const [data, setData] = useState({});
const { promiseInProgress } = usePromiseTracker();
const loadData = async () => {
await sleep(2000);
const res = await fetch("https://api.agify.io/?name=michael");
setData(await res.json());
};
useEffect(() => {
trackPromise(loadData());
return () => {};
}, []);
return <p>{promiseInProgress ? "loading" : data.name}</p>;
}
In the code above, we have 2 promises. One if the sleep
function call, which returns a promise. Then we have our fetch
request promises in the loadData
function.
We then use the trackPromise
function from react-promise-tracker
to track whether the promises are finished or not.
If one or more promises are loading, then we have the promiseInProgress
state is set to true
. When it’s true
, we show the ‘loading’ text. When the promises are done, we show the value of data.name
.
This is a great package that makes tracking the loading of promises easy. We can also add a delay
as follows to delay the loading of the promise as follows:
import React, { useEffect, useState } from "react";
import { usePromiseTracker, trackPromise } from "react-promise-tracker";
const sleep = ms => new Promise(r => setTimeout(r, ms));
export default function App() {
const [data, setData] = useState({});
const { promiseInProgress } = usePromiseTracker({ delay: 500 });
const loadData = async () => {
await sleep(2000);
const res = await fetch("https://api.agify.io/?name=michael");
setData(await res.json());
};
useEffect(() => {
trackPromise(loadData());
return () => {};
}, []);
return <p>{promiseInProgress ? "loading" : data.name}</p>;
}
In the code above, we have:
usePromiseTracker({ delay: 500 })
to prevent flickering when the component is loaded on high-speed connections.
Conclusion
In React function components, it isn’t immediately obvious where we place our code to load data. The correct way is to add callbacks to the useEffect
hook.
The 2nd argument of useEffect
will indicate when the callback will run. If it’s empty, then it’s run when the component first loads. If it’s not, then it’ll watch the value changes of the items in the array and runs the callback when any of them change.
We have to return a cleanup function to run clean up code if needed. Even if we don’t, we still need to return an empty function.
To add a loading indicator when promises load, we can use the react-promise-tracker
package to track that and render the loading indicator according to the state returned by this package.