Skip to content
wannacode

Conditions: empty, error, loading

In short: Conditional rendering shows only the part of the markup that matches the data (loading, error, empty or the list); in React with && and the ternary operator.

Problem

Loading, error and the list at once

The catalog has loaded. Under the heading, though, it says "Loading…", then "The products did not load.", then "There are no products in this category.", and only then come the products.

React

The Catalog component gets status and the products as props. But it does not look at them: it always draws all four parts. The buyer does not know what to believe, and a screen reader reads both the error and the products.

The idea

The part that matches the data

The catalog has four states: loading, error, empty and ready. Only the part for the current one should be on the screen. This is conditional rendering: the component decides what to return based on the data.

JSX has three ways, depending on how much changes:

// A whole different view: if and an early return
if (status === 'loading') return <p role="status">Loading…</p>;
// One of two: ? :
<p>{product.stock === 0 ? 'Sold out' : 'In stock'}</p>
// Something or nothing: &&
{status === 'error' && <p role="alert">The products did not load.</p>}

if is plain JavaScript: the component is a function and can return different JSX. ? : and && are expressions, so they can sit inside JSX, in {}.

The two libraries solve the same thing in their own way: React with JavaScript around the markup, Vue with attributes inside it. The result is the same: the element of a false condition is not in the DOM. It is not hidden, it does not exist.

Here status is given as a prop. How the catalog changes it by itself while loading from the API is in the module "Effects, data and forms".

See it

See which branch goes in

One value goes into the component, and only one branch goes into the DOM. Watch the "DOM" column on every change of status.

function Catalog({ status, products }) {
if (status === 'loading') return <p>Loading…</p>;
if (status === 'error') return <p>Error</p>;
if (status === 'empty') return <p>No products</p>;
return <ul>…</ul>;
}
// in the DOM, but hidden
<p hidden={status !== 'loading'}>Loading…</p>
Step by step, in words
  1. status comes into the component as a prop. It is 'loading': the first if matches, and the component returns only <p>Loading…</p>. The DOM gets only it.
  2. status becomes 'error'. The library draws again: now the second branch matches. <p>Loading…</p> leaves the DOM, and the error message comes in.
  3. 'empty': only the message that the category is empty.
  4. 'ready': none of the if statements matches, and the component gets to the last return: the list. One branch at a time is in the DOM.
  5. The other way: the element is always drawn, and the hidden attribute decides whether it is seen. With status loading, hidden is false and the text is visible.
  6. status becomes 'ready'. The element stays in the DOM, with what is in it, and only gets display: none. Nobody sees it, and a screen reader skips it.
  7. A condition with if, ? : or && removes the element from the DOM. hidden only hides it. Removing is the default; hiding is for something that is toggled often or must keep what was typed.

The first part is conditional rendering: on a new value the old element leaves the DOM and the new one comes in. The second part is hiding: the element is in the DOM the whole time and is only shown or hidden. The end of the lesson is about it.

Try it

The four states

Catalog draws everything at once. Make it so that every section has only the part of its state: "Loading…" for loading, the error for error, "There are no products in this category." for empty and the list for ready. The other parts must not be on the page.

React

Try it

Fill in

"Loading…" should show only while status is loading.

{status === 'loading'  <p role="status">Loading…</p>}

The idea

Sold out: a badge and a disabled button

A condition is not only for whole parts. In the product card it changes a text and an attribute:

{product.stock === 0 ? (
<p className="badge">Sold out</p>
) : (
<p>Only {product.stock} left</p>
)}
<button type="button" disabled={product.stock === 0}>Add</button>

disabled={true} puts the disabled attribute on, and disabled={false} removes it.

A disabled button cannot be pressed, does not take the focus with Tab, and a screen reader says that it is unavailable. The badge says why. A button that can be pressed and then says "out of stock" is worse: the buyer finds out only after the click.

Try it

Sold out

Make ProductCard show "Sold out" and disable the "Add" button when stock is 0. Otherwise it shows "Only 5 left" and the button works. The cheese is sold out.

React

The idea

The zero on the screen

&& does not return true or false. It returns the left side if that is falsy, and the right side if it is truthy. React draws numbers, 0 included, and does not draw false, null and undefined. So:

{items.length && <p>In the cart: {items.length}</p>} // empty cart: 0
{items.length > 0 && <p>In the cart: {items.length}</p>} // empty cart: nothing

Before && put a comparison that gives true or false: > 0, === 0, !== null. Another way is ? : with null for "nothing".

Try it

Find the bug

With an empty cart, the top corner says "0".

Pick the line with the bug.

The idea

Removed or hidden

Sometimes an element has to stay in the DOM and only not be seen. Here both panels have a field for a note. Open both, type something, close them and open them again.

React
In the first section the panel uses {removedOpen && …}: when closed it is gone, and when opened it is new, with an empty field. In the second it uses the attribute hidden={!hiddenOpen}: it stays in the DOM with display: none and keeps what was typed.

Removing is the default: fewer elements on the page, nothing hidden for a screen reader. Hiding is for parts that are opened and closed often or have to keep what was entered in them.

More precisely, if you are curious

hidden and CSS. hidden works through display: none from the browser's styles. If a CSS rule gives the element display: flex, the rule wins and the element is still visible. Then you hide it with a class or with style={{ display: 'none' }}.

Same place, same element. With {isSearch ? <input placeholder="Search" /> : <input placeholder="Code" />} both branches are an <input> in the same place. React keeps the element and only changes placeholder, and what was typed stays. If you need two different elements, give them different keys.

return null. A component that should draw nothing returns null.

Class components in old code do the same in the render() method.

aria-hidden is not hiding. aria-hidden="true" hides the element only from a screen reader, and it stays on the screen. Hiding from everybody is display: none: hidden, v-show or removing.

Exercise

The cart

App shows three carts: empty, with products and with a sold out product. Make Cart so that the empty one says "The cart is empty.", with no list and no total, and the "Place order" button is disabled. The full one shows the rows, "Total: €4.00" and a working button. The sold out product has a "Sold out" badge, above the button it says "Remove the sold out products to place the order." (role alert), and the button is disabled.

React

Test

Check yourself

1. The condition for "Loading…" is false. What is in its place in the DOM?
2. The cart is empty. What does {items.length && <p>In the cart: {items.length}</p>} draw?
3. What fits best to make the component show only "Loading…" while status is loading?
4. A panel with a note field is opened and closed often, and what was typed has to stay. Which fits better?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·