Playing with React Hooks - Async operation with Loading state
Recently I started learning React.js. While the framework has so many fascinating features, the particular one I liked is Hooks
. Although hooks is relatively new feature and isn't widely used yet, I tried to learn it with more hand-on approach by making a demo app comprising of loading state, a simulated async operation, and option boxes.
First, let's make a list of things we want to achieve,
- An option selector letting user to select desired user id
- A mocked network operation which will return whether the user with selected id is online or not
- Display the loading spinner while request is in progress
- Hide status section while network request is in progress
- Show the user online status once the request comes back
Let's start with the skeleton structure,
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const userIds = {
100: true,
200: false,
300: true,
400: false,
500: true
};
function UsersListDatabase() {
let optionRows = [];
for (const [key] of Object.entries(userIds)) {
optionRows.push(<option key={key} value={key}>{key}</option>);
}
return (
<div>
<select>
{optionRows}
</select>
<div>
</div>
<div></div>
</div>
);
}
ReactDOM.render(
<React.StrictMode>
<UsersListDatabase />
</React.StrictMode>,
document.getElementById("root")
);
So what we did here?
- Added necessary React imports
- Declared a list of user ids with online status
- Created a React component named
UsersListDatabase
which reads fromuserIds
array and created a select HTML section insideUsersListDatabase
React component
But this is just a raw HTML content. Next we will add an even listener with onChange
event on option selector. Any time option is changed, we need to store it somewhere. To store it, we will create a state with React hooks. We're going to call this state as userId
We will give it the id of first user and it will change every time user selection changes,
function UsersListDatabase() {
const [userId, setUserId] = useState(100);
....
....
return (
<div>
<select
onChange={e => setUserId(Number(e.target.value))}
>
{optionRows}
</select>
....
....
);
}
Currently there is no action happening every time the userId
changes. Instead we want to send a network request every time this value changes and wait for the output. To achieve it, we will create a new custom hook with name useOnlineStatus
(Please note that it's React convention to begin hook name with keyword use
)
function useOnlineStatus(userId) {
const [isLoading, setIsLoading] = useState(false);
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function fetchStatus(userId) {
setIsLoading(true);
setTimeout(() => {
setIsOnline(userIds[userId]);
setIsLoading(false);
}, 1000);
}
fetchStatus(userId);
}, [userId]);
return [isOnline, isLoading];
}
function UsersListDatabase() {
const [userId, setUserId] = useState(100);
const [isOnline, isLoading] = useOnlineStatus(userId);
....
return (
<div>
<select onChange={e => setUserId(Number(e.target.value))}>
{optionRows}
</select>
<div>
</div>
<div></div>
</div>
);
}
What's happening here?
We want to trigger an action when user changes the selection option. In our case, hit the network request. This is done by useOnlineStatus
hook. But how do we trigger that change?
- When user changes the selected option, an
onChange
event gets fired which sets theuserId
with currently selected id - Since
useOnlineStatus
hook is dependent onuserId
the hook gets executed - When
useOnlineStatus
gets executed, it sets two internal hook states -isLoading
andisOnline
- Due to setting of these states, a lifecycle method
useEffect
gets called which in turn calls the functionfetchStatus
with input user id - As the request is executing, we set the stats of
isLoading
to true - Once request is finished, we set
isLoading
to false and check theuserIds
array to setisOnline
status depending on whether user with input user id is online or not
Please note how we're also passinguserId
in an array as a trailing parameter touseEffect
method. This is to prevent the infinite loop. As long as theuserId
parameter remains the same, this lifecycle method will be called just once
Next, we have UsersListDatabase
component using these states outside useOnlineStatus
hook with following line
const [isOnline, isLoading] = useOnlineStatus(userId);
As soon as these values change inside useOnlineStatus
hook, they are immediately transferred inside these two constants.
This is great! But we need to have some user presentable information on our web page and these flags won't help. To help with this transformation, we will create another custom hook useUserPresentableInformation
which will take two parameters (isOnline, isLoading)
received from previous step and pass them to next step.
function useUserPresentableInformation(isOnline, isLoading) {
const [loadingText, setLoadingText] = useState('');
useEffect(() => {
setLoadingText(isLoading ? 'Loading' : 'Loaded');
}, [isLoading]);
const [statusBackgroundColor, setStatusBackgroundColor] = useState('gray');
const [statusText, setStatusText] = useState('');
useEffect(() => {
if (isLoading === true) {
setStatusBackgroundColor('gray');
setStatusText('');
} else {
setStatusBackgroundColor(isOnline ? 'green' : 'red');
setStatusText(isOnline ? 'Online' : 'Offline');
}
}, [isLoading, isOnline]);
return [loadingText, statusBackgroundColor, statusText];
}
function UsersListDatabase() {
const [userId, setUserId] = useState(100);
const [isOnline, isLoading] = useOnlineStatus(userId);
const [loadingText, buttonBackgroundColor, statusText] = useUserPresentableInformation(isOnline, isLoading);
...
...
}
useUserPresentableInformation
takes two input statesisLoading
andisOnline
as indirectly received fromuseOnlineStatus
hook- Transforms them into user presentable information such as loading text, background color for user status, and status text - Whether user is online or not
We reached at pretty good state. But we are still not using any of the values returned by useUserPresentableInformation
so that is something we are going to integrate into our component next.
In first step, we created a skeleton HTML structure and now is the right time to start integrating state values in top level UsersListDatabase
React component
function UsersListDatabase() {
....
....
return (
<div>
<select
onChange={e => setUserId(Number(e.target.value))}
>
{optionRows}
</select>
<div style={{ backgroundColor: buttonBackgroundColor }}>
{statusText}
</div>
<div>{loadingText}</div>
</div>
);
....
....
}
ReactDOM.render(
<React.StrictMode>
<UsersListDatabase />
</React.StrictMode>,
document.getElementById("root")
);
And voilà - We have the full source code!
Here's the short video of demo,
The full source code can be found on codesandbox.io at this link. Please let me know how you liked this post and any comments/suggestions that can be helpful. I am still learning React and its nuances, and definitely appreciate any opportunity to improve the demo code and my understanding of React in terms of best practices and clean code.
References: React Official Documentation