Set a Minimum Display Time for Material-UI Skeleton Loader

The Material-UI Skeleton gives a great visual treat to users of your website or app. It’s strength is that it makes the user feel as though the app is useful and useable before critical loading is completed.

Let’s configure a Material-UI Skeleton Loader to display for 1 second OR the loading time of an API call, whichever is greater.

Material-UI Skeleton Loader

Typically users don’t need a loading icon if load time is less than 1 second, but if you are uncertain how long an API may take to respond, you may want to avoid flashing a component on the screen before showing a loading indicator.

Alternatively, you could show nothing at all for one second before choosing to show a loading indicator. Or you could show the indicator immediately, which is what I will show in this demo. Let’s get to coding!

The finished Code Sandbox is in the Resources section. We’ll walk through the code step-by-step below.

Skeleton Code

We’ll start with these constants:

const [minimumTime, setMinimumTime] = useState(500);
const [minimumTimeElapsed, setMinimumTimeElapsed] = useState(true);
const [loading, setLoading] = useState(false);

If this weren’t a demo, you would have a const minimumTime = 500; (or whatever desired value) instead of the state const above. In this demo, the minimum time is adjustable by the user.

minimumTimeElapsed is one of the two checks performed by the logic to determine if the <Skeleton /> should be shown. loading is the other variable checked, and it is true or false depending on if our simulated API call has completed.

Below is the ternary statement in our JSX that uses the two constants mentioned above. It either shows the <Skeleton/> component or the <JobTable/> component if the user-specified minimum visible time has not elapsed or the simulated loading has not finished.

{
   !minimumTimeElapsed || loading 
   ? <Skeleton style={{ height: 200 }} />
   : <JobTable />
}

Notice how I set the Skeleton height. I played around with trying to dynamically determine the height, but it was difficult to match the Skeleton and table in a reliable way.

Given that our hypothetical user requirements state that it should be visible if a) a minimum time has not elapsed, or b) the API call has not completed yet, we need a way to determine when to flip the state. The state is time based, so we will have to use setTimeout within a callback:

const restartTimeout = useCallback(() => {
    setMinimumTimeElapsed(false);
    setTimeout(() => {
      setMinimumTimeElapsed(true);
    }, minimumTime);

    //simulate random load time between 0 and 5 seconds
    const randomLoadTime = Math.random() * 5000;
    setLoading(true);
    //simulate loading
    setTimeout(() => {
      setLoading(false);
    }, randomLoadTime);
  }, [setMinimumTimeElapsed, setLoading, minimumTime]);

The restartTimeout function takes a user-specified minimum time and flips minimumTimeElapsed to true when the setTimeout callback is called.

The setTimeout that flips loading to false is only necessary for demo purposes. In a real situation, your code would instead likely be observing a loading observable in a store.

Finally, the JSX where we bring the Skeleton together with TextField, Button, and Table components:

<div className="App">
      <h2>Skeleton Loader with Minimum Time</h2>
      <div style={{ maxWidth: "100%", paddingTop: 12 }}>
        <TextField
          variant={"outlined"}
          style={{ margin: 8, minWidth: 250 }}
          label={"Set minimum wait"}
          onChange={(e) => setMinimumTime(e.target.value)}
        />
        <Button
          variant="outlined"
          style={{ margin: 8, minWidth: 250 }}
          onClick={() => {
            restartTimeout();
          }}
        >
          Restart Skeleton Timer
        </Button>
        {!minimumTimeElapsed || loading ? (
          <Skeleton style={{ height: 200 }} />
        ) : (
          <JobTable />
        )}
      </div>
    </div>

The Result

Our <Skeleton /> now shows for at least half a second or the loading time of the simulated API call, whichever is longer. Our constants are clearly named so the logic is conveyed to future devs looking at this code.

If you can confirm that the logic is correct and the code is clean, consider your work a job well done.

Consider using the Skeleton component to give users a better experience while waiting for tables and datagrids to load.

Resources

Code Sandbox Link

Test your JavaScript knowledge with these 50 challenging interview questions!

Docs Link

Share this post:

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.