Using React useEffect hook with an API call

Hi all, this is an introduction to the react hook useEffect and how to use it in the simple context of functional React and an API call (async). It's part #2 of a 7-part group of articles on React hooks and how I learned them while building a simple app.
When you're a new React learner, it can be hard to find examples and tutorials that help you for your specific needs. So I thought I'd share my react practice in case that helps anyone out there :)
2 - The React useEffect hook
In this article, we build onto the first article's content and continue developing our simple app, this time improving it with useEffect hooks.
In case you didn't read the first article, here's a summary of the app we built so far: a simple react app that calls the github API, providing it a username in an input field, and the API returns a list of the public repos for that username. We then display the list of repos under the input field and we can select one repo from the list and see more info about it on the right hand side of the page. This app was built and managed with the react hook useState, and we also touched on the useEffect hook briefly as we added an action to be triggered on load of the page (and we will review this a bit more here in our useEffect #1).
You can see the code for part #1 here.
In this article, we will bring our app further along and add some features such as:
- automate the username search, so it happens as I type the name in the field instead of clicking a button
- add a field showing me the number of public repos found for that username
- add a colour indicator on the details of the selected repo, so I can more easily see if that repo was modified recently or not. And our aim here is to implement these three additional features and use the useEffect hook for that.
In the rest of the article, we will assume that we are familiar with:
- the hook useState explained in the previous article
- the general concept of React components and how their rendering cycle takes place
- async functions and other javaScript core concepts and syntax
Syntax of the useEffect hook
The useEffect hook has the following basic syntax:
useEffect ( callbackFunction , [dependency] )
Its syntax is a function call with 2 arguments, which are:
- a dependency: this is the items you want to monitor. It can be one or several items, that's why it is provided as an array
- a callbackFunction: this is the function you want to execute when the dependencies do change This useEffect will look at the dependency and compare it to the version used for the last render of the page. If it is different, it will trigger the callback function.
Here is a link to a good freecodecamp article if you are an absolute beginners on React components and the useEffect react hook.
Different behaviour for different hook syntax
Before we dive into our useEffect hooks and the code we need for our app, let's review the different syntaxes we can use to write a useEffect.
The dependency can be provided in 3 different ways:
not provided at all. The syntax would become:
useEffect ( callbackFunction )In that case, the useEffect is not monitoring anything and it is triggered as every initial render/page load, and at every re-render of the component (if triggered by something else).
provided as an empty dependency array. The syntax would be:
useEffect ( callbackFunction, [] )In that case, the useEffect is not monitoring anything and is only triggered on initial render of the component/page load.
provided with actual dependencies to monitor. The syntax would be for example:
useEffect ( callbackFunction, [username] )In that case, the useEffect is monitoring the username state variable, and it is triggered once on initial render/page load (as per every useEffect) and every time the username variable changes.
Triggering a useEffect on load of the page (useEffect #1)
In the last article, we touched on the topic of triggering a useEffect on load as we wanted to have some list displayed on load of the page so it looked a bit less sad.
For that, we wrote the following code:
useEffect(() => {
getRepos("am0031");
}, []);
Here we are not monitoring any specific items, we only want this useEffect to be triggered once on initial render of the page so we pass an empty dependency array. This useEffect will trigger the getRepos function which will populate the repos state variable. Once the repos state variable is updated, the repo list's display element which uses that state will also be updated.
Triggering a useEffect on change of the text in the input field (useEffect #2)
The input field has an event set up to monitor the changes in that field and assign them to the username state variable.
The input field is as follow:
<input
style={{ width: "400px" }}
id="search-input"
value={username}
placeholder="Enter github username"
onChange={handleChange}
></input>
And the handleChange function is as follow:
const handleChange = (event) => {
setUsername(event.target.value);
};
Now, to be able to dynamically search alongside the username change, we can set up the following useEffect:
useEffect(() => {
setSelectedRepo();
getRepos(username);
}, [username]);
This way, every time the username is updated with a new letter, the useEffect will be triggered and call the github api with whatever is saved in the username variable.
Here are additional aspects to consider based on what I would like to see on page load:
- I still would like to see my own repos on load (as per my useEffect #1 - see above) before I perform any search (before I change the username).
- Ideally, I would like my input field to be empty (just showing the placeholder text) until I actually start typing in it
These preferences (which are really arbitrary criteria on my part) can be achieved in various ways:
1- I can choose to position useEffect #2 in the code before the useEffect #1. On initial render, we know that all our useEffect hooks will be triggered and will be executed in the order they are written in our code. And as we have 2 useEffect which update the same repos state variable, we must trigger them in a suitable order.
//useEffect for username change - useEffect #2
useEffect(() => {
setSelectedRepo();
getRepos(username);
}, [username]);
//useEffect for onload repos population - useEffect #1
useEffect(() => {
getRepos("am0031");
}, []);
When placed in that order, actions happen as follow: useEffect #2 get the repos for the string saved in the username variable. On initial render, the username variable is an empty string, so the getRepos function will call the github api passing it an empty string and the returned empty array of repos will be placed in the repos state variable. useEffect #1 get my repos because I pass it the hard coded value of my own username. This value is independent from the username state variable. On initial render, this hook is triggered and the returned array of my repos is placed in the repos state variable.
On completion of the first render, the repos variable contains the my repos list.
2- I can use an additional variable to track when a user is searching. For that we would create an additional state variable isSearching which would start as false and change to true when we start typing in the input field, and we would have the useEffect #2 monitor for both username and isSearching states. This way we don't have to worry in which order we place our useEffects in the code because:
- on initial render: useEffect #1 is triggered and populates the repos variable with my repos list, useEffect #2 is checked but the conditions are not met so there is no call to the API.
- on input field change: useEffect #1 doesn't get triggered, and useEffect#2 is triggered and populates the repos variable with the result from the api. With this set up, only 1 useEffect takes place at each render.
The code for this would be:
We add a new state variable:
const [isSearching, setIsSearching] = useState(false);
And the handleChange function is modified as follow:
const handleChange = (event) => {
if (!isSearching) setIsSearching(true);
setUsername(event.target.value);
};
And the useEffect is modified as follow:
useEffect(() => {
if (isSearching === true) {
setSelectedRepo();
getRepos(username);
}
}, [username]);
Note: These are my preferences, but it's entirely up to you if you'd like your app to behave differently here. For example, you might not care that the input field shows as empty on initial render. In that case, you can simplify the code and have only 1 useEffect and populate your username variable with your own username as the initial state. That would fulfil both actions of giving you your repos on render, and giving you the search results when typing in the input field, you'd just have to erase your username from the field first.
Note 2: the example code in the github repo is built using the option 2 with an additional variable.
Triggering a useEffect on change of the repos received from the API (useEffect #3)
We're adding a field on the page where we will be able to see how many repos we have in the repos array at any given time. For the purpose of practicing more useEffect hooks, we will do this with a useEffect, but this can be done more simply by just displaying repos.length.
To get started, we create a new variable where we will store that number:
const [repoCount, setRepoCount] = useState();
And we add a useEffect which will monitor the repos variable:
//useEffect for repo count - useEffect #3
useEffect(() => {
if (repos) {
setRepoCount(repos.length);
}
}, [repos]);
Every time the repos state variable changes, this useEffect is triggered and updates the value in repoCount state variable.
Finally we display the repoCount by adding a field above our display list:
{repoCount && <h3>{repoCount} repos</h3>}
Triggering a useEffect on change of the repo selected (useEffect #4)
Finally, we're adding a colour indicator on the details of the selected repo, more specifically on the field "last updated". Our logic will be set up as follow:
- the text colour is styled with a colour depending on how far in the past the date is
- if the date is less than 7 days ago, it will appear green
- if the date is between 7 and 21 days ago, it will appear orange
- if the date is more than 21 days ago, it will appear red
We set up a new state variable to store the date style:
const [timeStyle, setTimeStyle] = useState("black");
This state variable can be passed to the display item in the style definition "color" as a template string:
{selectedRepo && (
<>
<h3>{selectedRepo.name}</h3>
<h3 style={{ color: `${timeStyle}` }}>
Last updated: {selectedRepo.updated_at}
</h3>
<h3>URL: {selectedRepo.url}</h3>
</>
)}
Every time we select a new repo from the list, the selectedRepo state variable is updated. So we can set up a useEffect to be triggered on update of selectedRepo, and this useEffect would :
- get the updated_at date from the selectedRepo
- calculate the timeframe between the updated_at date and today
- check the timeframe against our criteria and assign the corresponding colour in string format to the timeStyle variable.
The code for this useEffect would look like this:
//useEffect for selected repo display - useEffect #4
useEffect(() => {
if (selectedRepo) {
const updateDay = new Date(selectedRepo.updated_at);
const timeframe = Math.floor(
(new Date() - updateDay) / (1000 * 3600 * 24)
);
if (timeframe < 7) {
setTimeStyle("green");
} else if (timeframe < 21) {
setTimeStyle("orange");
} else {
setTimeStyle("red");
}
}
}, [selectedRepo]);
With the timeStyle state variable updated, the display will update and show this field in the right colour.
So there we have it, our additional functionalities and styling all set up with useEffect hooks!
What to watch out for when using useEffect
It's important to mention where to be careful when using useEffect:
- a useEffect cannot monitor a state and also update that same state variable
- when several useEffect happen in a sequence, it's important to have a clear understanding of what happens in that sequence in order to avoid creating a circular chain of events. For example, let's imagine this chain of events: useEffect( updateB, [A]) -> useEffect( updateC, [B]) -> useEffect(updateA, [C]) This would cause infinite re-renders and break the application.
The combination of useEffect and useState
When using useEffect, it's also important to mention that the dependencies to monitor need to be state variables or props passed to the component. The hook will not execute correctly if the dependency is of a different type (like for example a let variable declared before the hook).
Going further
The example code for this article can be found in this github repo. You can easily clone it, play with it and even add to it :)
In the next article, we will explore the useReducer hook and look to refactor our app with it.

