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
UsersListDatabasewhich reads fromuserIdsarray and created a select HTML section insideUsersListDatabaseReact 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
onChangeevent gets fired which sets theuserIdwith currently selected id - Since
useOnlineStatushook is dependent onuserIdthe hook gets executed - When
useOnlineStatusgets executed, it sets two internal hook states -isLoadingandisOnline - Due to setting of these states, a lifecycle method
useEffectgets called which in turn calls the functionfetchStatuswith input user id - As the request is executing, we set the stats of
isLoadingto true - Once request is finished, we set
isLoadingto false and check theuserIdsarray to setisOnlinestatus depending on whether user with input user id is online or not
Please note how we're also passinguserIdin an array as a trailing parameter touseEffectmethod. This is to prevent the infinite loop. As long as theuserIdparameter 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);
...
...
}
useUserPresentableInformationtakes two input statesisLoadingandisOnlineas indirectly received fromuseOnlineStatushook- 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