Playing with React Hooks - Async operation with Loading state

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,

  1. An option selector letting user to select desired user id
  2. A mocked network operation which will return whether the user with selected id is online or not
  3. Display the loading spinner while request is in progress
  4. Hide status section while network request is in progress
  5. 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?

  1. Added necessary React imports
  2. Declared a list of user ids with online status
  3. Created a React component named UsersListDatabase which reads from userIds array and created a select HTML section inside UsersListDatabase 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?

  1. When user changes the selected option, an onChange event gets fired which sets the userId with currently selected id
  2. Since useOnlineStatus hook is dependent on userId the hook gets executed
  3. When useOnlineStatus gets executed, it sets two internal hook states - isLoading and isOnline
  4. Due to setting of these states, a lifecycle method useEffect gets called which in turn calls the function fetchStatus with input user id
  5. As the request is executing, we set the stats of isLoading to true
  6. Once request is finished, we set isLoading to false and check the userIds array to set isOnline status depending on whether user with input user id is online or not
Please note how we're also passing userId in an array as a trailing parameter to useEffect method. This is to prevent the infinite loop. As long as the userId 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);
  
  ...
  ...
  
}
  1. useUserPresentableInformation takes two input states isLoading and isOnline as indirectly received from useOnlineStatus hook
  2. 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