Using React useReducer hook with an API call
How I built a simple react app using the useReducer hook coupled with an async API fetch

Hi all, this is an introduction to the react hook useReducer and how to use it in the simple context of functional React and an API call (async). It's part #3 of a 7-part group of articles on React hooks and how I learned them while building and changing 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 :)
3 - The React useReducer hook
In this article, we build onto the first 2 articles and continue developing our simple app, this time improving it by swapping some of our previous hooks with a useReducer hook.
In case you didn't read the first 2 articles, 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 hooks useState and useEffect. We also had a couple of effects such as: display my own repos as default on load of the page, display the number of repos above the repos list, and change the colour of the updated_at date depending on how far in the past it is.
You can see the part #2 article here and the part #2 code here.
In this part #3, we will bring our app further along and adjust some states and functions, such as:
- manage the main states (username, repos, selectedRepo) with useReducer
- simplify the repo count display and the colour styling on the updated_at date. We had set up some useEffect for these, but they can be managed in a simpler way.
If you want to jump right to the example code, you can find it here on my repo react-reducer.
In the rest of the article, we will assume that we are familiar with:
- the hooks useState and useEffect explained in the previous articles
- 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 useReducer hook
The useReducer hook has the following base syntax:
useReducer(<reducerFunction>, <initialState>)
with:
- the reducerFunction containing the logic we want to apply to manage our states
- the initialState object which compiles all the initial values of our states. This hook returns the current state and a dispatch method.
How to update our states to a useReducer state
In the context of our app, this means that we would proceed with the following replacement:
Previous set up of states with useState:
const [repos, setRepos] = useState();
const [selectedRepo, setSelectedRepo] = useState();
const [username, setUsername] = useState("");
const [isSearching, setIsSearching] = useState(false);
New set up with useReducer and initial state:
1- Set up the initial state object:
const initialState = {
repos: undefined,
selectedRepo: undefined,
username: "",
isSearching: false,
};
2- Set up the reducer function:
const reducer = (state, action) => {
switch (action.type) {
case "repos":
return { ...state, selectedRepo: undefined, repos: action.newRepos };
case "selected":
return { ...state, selectedRepo: action.selection };
case "username":
return { ...state, username: action.username };
case "searching":
return {
...state,
isSearching: action.isSearching,
};
default:
throw new Error();
}
};
Here, we use a switch case syntax, where each case is given a name which describes the type of action to be performed. For each case, the relevant fields of the state object are updated and the full state object is returned.
3- Set up the useReducer hook:
const [state, dispatch] = useReducer(reducer, initialState);
Here we set up our hook so that we can call the dispatch function later in our code to update our state variable.
Note: these 3 parts need to be listed in that order in your code.
How to update our functions triggered on change/on click
Once the base set up is in place, the various functions called throughout our code need to be adjusted so that they can interact with our state. To achieve this, the functions will be adjusted as follow:
- they will trigger the dispatch function
- they will pass an object to it containing several key/value pairs: the first key/value pair will always be the type of action to be performed, and the rest will be the values to update in the state object.
Updating the username on change of the input field (action type "searching" and "username")
The input field has an onChange event which monitors for any character change:
<input
style={{ width: "400px" }}
id="search-input"
value={state.username}
placeholder="Enter github username"
onChange={handleChange}
></input>
Previous version of function handleChange:
const handleChange = (event) => {
if (!isSearching) setIsSearching(true);
setUsername(event.target.value);
};
Now that the username is a property in the state object and not its own variable, we can access it by using the usual javaScript object dot notation, which here would look like "state.variableName". So accessing the username becomes state.username.
As the individual states became properties of the state object, they lost their associated helper function which we would call to modify them. Now, instead we call the dispatch function, and it is in the arguments passed to this function that we specify which action we want to perform and which property we want to change.
As a result, the handleChange function would now look like this:
const handleChange = (event) => {
if (!state.isSearching) {
dispatch({
type: "searching",
isSearching: true,
});
}
dispatch({ type: "username", username: event.target.value });
};
Here, we are performing the same changes as we did in our previous set up, which are:
- we want to change isSearching to true when the user starts typing a first letter in the input field
- we want to update the username value to the letters that have been typed in the input field as soon as there is 1 letter and every time there's a change in input
For each situation, we use the dispatch function, making sure to pass the action object containing the relevant type (= case name), and the relevant values to update (key/value pair).
Updating the repos on username change (action type "repos")
As the username changes, the repos will be updated accordingly. This was previously set up with the following useEffect:
useEffect(() => {
if (isSearching === true) {
setSelectedRepo();
getRepos(username);
}
}, [username]);
With the new state object, the syntax is adjusted as follow:
useEffect(() => {
if (state.isSearching === true) {
getReposAndDispatch(state.username);
}
}, [state.username]);
This useEffect still monitors the username and will trigger an API call when the both conditions are met: username has changed and isSearching is true.
The async function in charge of the API call is also updated to ensure compatibility with the dispatch action that follows. For that, we wrapped the getRepos function into the getReposAndDispatch function where:
- we call the getRepos function first and await the response (async part)
- we use the response to update the state property repos
Previously we had:
const getRepos = async (username) => {
const response = await fetch(
`https://api.github.com/users/${username}/repos`
).then((response) => response.json());
setRepos(response);
};
After update, the function would look like this:
const getReposAndDispatch = async (username) => {
const getRepos = async (username) => {
const response = await fetch(
`https://api.github.com/users/${username}/repos`
).then((response) => response.json());
return response;
};
const newRepos = await getRepos(username); //step 1 - asynchronous call -> await response
dispatch({ type: "repos", newRepos }); //step 2 - use response to update state property
};
Note: the dispatch part of the function follows the asynchronous call but is not asynchronous itself and cannot be coupled with the "await" keyword.
Once the state property repos has changed, the repo list display dependent on that state property is re-rendered.
Updating the repos on page load (action type "repos")
As per our previous setup, this useEffect uses the same function as the useEffect triggered on username change, but we are passing it a hard coded value of my own username. The logic remains the same, we only need to update the function called to our new wrapping function getReposAndDispatch:
Previous:
useEffect(() => {
getRepos("am0031");
}, []);
After update:
useEffect(() => {
getReposAndDispatch("am0031");
}, []);
Updating the selected repo on click in repo list (action type "selected")
When clicking on an item in the repo list, we trigger the onRepoClick function. The same filter as before is performed to extract info of the selected item, and the action is now going through the dispatch function which updates the state property selectedRepo :
Previous:
const onRepoClick = (event) => {
const selection = repos.filter(
(item) => item.id === parseInt(event.target.id)
)[0];
setSelectedRepo(selection);
};
After update:
const onRepoClick = (event) => {
const selection = state.repos.filter(
(item) => item.id === parseInt(event.target.id)
)[0];
dispatch({ type: "selected", selection });
};
With these adjustments to our functions, we are channelling our changes to any of the 4 state properties through the relevant case in the 4 possible cases in the reducer function.
How to simplify displays for repo count and date colouring
There are times when using useEffect or adding another property to the state object is not needed because the displaying or formatting can be easily done with simple jsx.
Simplifying the repo count display
This information is available by simply getting the length of the repos array stores in the state property repos. So we can very simply adjust the display to:
{state.repos && <h3>{state.repos.length} repos</h3>}
This display will be updated every time the repos get updated.
Simplifying the updated_at colour formatting
This date is styled by passing a string to a style element. The relevant string can be passed directly to the display element without going through a state or a useEffect.
In article #2, for the purpose of demonstrating useEffect, we had put in place the following:
A useEffect monitoring the selectedRepo variable and updating the timeStyle variable:
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]);
And a display element using the timeStyle variable:
<h3 style={{ color: `${timeStyle}` }}>
Last updated: {selectedRepo.updated_at}
</h3>
But instead, we can set up a setTimeStyle function which is directly invoked by the display element and would return the correct string directly to it. This would now look like this:
The function:
const setTimeStyle = (selectedRepo) => {
if (selectedRepo) {
const updateDay = new Date(selectedRepo.updated_at);
const timeframe = Math.floor(
(new Date() - updateDay) / (1000 * 3600 * 24)
);
if (timeframe < 7) {
return "green";
} else if (timeframe < 21) {
return "orange";
} else {
return "red";
}
}
};
And its invocation in the display element:
<h3 style={{ color: `${setTimeStyle(state.selectedRepo)}`}}>
Last updated: {state.selectedRepo.updated_at}
</h3>
General update of display elements
When transitioning from useState/useEffect to useReducer, it's important to also check all the variables in the display elements to make sure that all the states who bacame stat properties are now called with the correct syntax (state.xxx).
Going further
You can find the complete code for this example app here.
With this transition to useReducer, we now manage changes on the page through the reducer function and we don't have anymore useState. Of course, there might be times when useState is more appropriate. In any case, the useEffect hook will come into play if you need to monitor changes and react to them.
In our next article, we will look at the useRef hook and how to expand our app with it.

