This isn't something I was going to blog about, but after seeing the same issue a few times recently (although to be fair, last in a mobile game), I thought I'd share it with my audience. I apologize if the title isn't the best as it was a hard issue to describe, so let me begin by demonstrating the problem, and then the (hopefully) obvious solution.

Loading Data #

Here's a super simple example of a web page that loads some data from the API. In this case, it's the Star Wars API which, unfortunately, has been pretty slow recently. On the flip side, that helps illustrate that issue.

The HTML is just an h2 and an empty ul and the JavaScript is fairly simple:

document.addEventListener('DOMContentLoaded', init, false);

async function init() {
	let $shipList = document.querySelector('#shipsList');
	let shipRequest = await fetch('');
	let ships = await shipRequest.json();
	if(ships.count === 0) {
		$shipList.outerHTML = 'There are no ships. :(';	
	} else {
		let html = ships.results.reduce((prev, cur) => {
			return prev + `<li>${}</li>\n`;
		}, '');
		$shipList.innerHTML = html;

The result of the API call will contain a count variable, and if it's zero, it means we have no data. If it's anything else, we can render a list of ships. You can see this in action below:

See the Pen Untitled by Raymond Camden (@cfjedimaster) on CodePen.

If you got to this part of my article and the data was already there, just hit the nice little Rerun button in the bottom right to see how there's no indication that anything is loading at all. The user may think the page is broken and navigate away.

The solution is to simply add a loading message. Most folks I assume know this already, but let's just fix that quickly. Since my JavaScript blows away the inside of my list when the data is loaded, I'll fix the issue with HTML:

<ul id="shipsList">
	<li><i>Loading ships...</i></li>

Here's that version, and again, hit Rerun to see it properly.

See the Pen Untitled by Raymond Camden (@cfjedimaster) on CodePen.

Cool. My expectation is that most people inherently get this, but if not, hopefully I've taught you an good way to handle things like this. But - let's consider an alternative.

Loading Data - Part Deux #

In the previous example, it's important to remember that there isn't two states (loading and done loading), but rather three: Loading, Ships, and No Ships. Let's consider a variant of the previous demo that makes use of Alpine.js. In my HTML, I'm going to handle only two states - no ships or ships:

<div x-data="app" x-cloak>
	<div x-show="ships">
		<template x-for="ship in ships">
			<li x-text=""></li>
	<div x-show="!ships">
		There are no ships.

My JavaScript simply fills the ship data on load:

document.addEventListener('alpine:init', () => {'app', () => ({
		async init() {
			let shipRequest = await fetch('');
			let shipData = await shipRequest.json();
			if(shipData.count) this.ships = shipData.results;

And now if you run this version, you see the issue (and again, smash that Rerun button):

See the Pen Untitled by Raymond Camden (@cfjedimaster) on CodePen.

You'll see the page load up and clearly say, "There are no ships", while that is clearly false. We don't know if there aren't ships!

Now, I feel like this is an obvious thing to avoid, but like I said, I've seen it a few times now so I figured it was time to talk about it.

As a final note, here's a corrected version of the previous example. In this one, I use a new variable, loading, and toggle the DOM between showing a loading message or one of the two results (ships or now ships):

See the Pen Loading Post V4 by Raymond Camden (@cfjedimaster) on CodePen.