Using the Hidden Attribute with React

Joseph Chamochumbi
Evolve Technology
Published in
6 min readJun 12, 2019

--

Every time a React component mounts, updates or is unmounted, React executes two phases:

  • A render phase
  • A commit phase
Photo by Med Badr Chemmaoui on Unsplash

There is a cause-effect relation between these two. The render phase is a pure, side-effect free phase, in which React collects updates, or attempts to set state on components.

In the future you’ll be able to bail on render phases.

The commit phase is responsible for running side-effects, and committing changes to the DOM. After React commits to the DOM, the Browser’s job is to apply CSS styles, layout calculations, paint pixels and compose your elements into layers.

That’s a lot of work!

Luckily, there is a DOM attribute called hidden, which you can use to get React to commit changes to the DOM, while also telling the browser not to run its own work, just yet.

It is equivalent to display: none, in fact, setting a display property on hidden elements overrides the attribute!

You may think that visibility:none should work the same way, but this is a CSS property, which means the browser it’s actually execute its work chain.

A demonstration

Even though MDN recommends not to use hidden to hide panels, it is a great way to show how to take advantage of this attribute.

In this demo, there are three tabs:

  • The Landing tab, simply displays a message.
  • The Bitcoin tab, loads data from CoinDesk and shows it in a Victory Chart.
  • The Pokémon tab, loads Pokémon from PokeApi and shows their sprites.

Show me the code

Each tab is a separate JavaScript bundle to React's lazy. Since the Bitcoin tab uses Victory Charts, the bundle is heavy and will most likely load last. The demo uses Parcel to bundle the whole application with zero configuration. The screenshots below are taken under Fast 3G and 6x slowdown on CPU.

Suspense Landing mounts.

After each bundle is loaded, it is parsed, then the actual component is mounted and its own life cycles methods are executed. On mount, the Landing tab does nothing special, however, in the Pokemon tab, fire type Pokémon are queried, and in the Bitcoin tab a request for the BTC/SEK rates during the last month is created.

The Bitcoin tab takes a lot of time to parse after its lazily loaded.

All of this is done before the user has switched tabs. Under normal network and CPU conditions, it all happens as they read the Landing tab message.

In the Pokémon tab, elements with an even index order load their Pokémon sprite as CSS background-image, while the ones with an odd index load the sprite as an img HTML element.

On the left the actual Landing mounts. To the right the actual Pokémon component also mounts.

To see that the Browser is not actually spending resources in rendering hidden elements. You can observe that React commits its work to the DOM, because sprites for Pokémon with odd indexes are fetched, since these are specified in the src attribute for the img element.

Notice that the Bitcoin data is also fetched.

On the left, Bitcoin tab mounts. To the right Pokémon tab updates and begins to fetch even indexed images.

In contrast, sprites for Pokémon with even indexes are not fetched, because these are used as a CSS background-image property and the hidden attribute tells the browser to not run it, just yet.

As soon as you switch to the Pokémon tab, the even indexed images are fetched. If you are in the demo page, open the network tab in your Browser’s developer tools to see it yourself.

What if you switch tabs quickly? You’ll either see the fallback, or simply see the rendered component.

All of the network traffic for the demo app. First serve our entry point, then lazy components, finally fetch Pokémon sprites and bitcoin rates data.

Details of Implementation

In the demo, the main App component looks like this:

App component. The labels could be plain strings.

It loads the Tabs component with three Tab children, the label prop on each will be used to generate the control button that activates the Tab.

There is an advantage to passing a React component as label prop. Read more about it here.

Each Tab itself has children, which are Suspense React components. In case you have not used lazy and Suspense before, these are defined like so:

Lazy example.

Now the implementation of the Tabs component. It controls which Tab children is shown, and it also generates the controls to switch between tabs.

To generate the controls the React Top Level API is quite convenient. For instance, React.Children.map allows us to pass children as first argument, and a function to map over each child, as second argument.

The mapping function creates a button HTML element that when clicked sets the child as current tab. The button also shows the label prop passed to Tab.

It is not absolutely necessary, but these controls are memo’d with empty dependencies, since they do not change when the current tab selection changes.

Tabs component using the hidden HTML attribute.

Another React top level API is React.Children.toArray, which returns children as an array. In this case, to generate the Tabs content, map over the array, wrapping all children with a div HTML element, where the hidden attribute equals the strict inequality result of index and current.

It is generally a bad practice to use the index as key for React elements, but for this demonstration it is good enough. Good practice would be to use a unique id prop, or the title prop, if it could be guaranteed to be unique.

The Tab component simply renders the title prop and its children.

Tab component.

Rendering the children, begins the suspenseful loading of each tab. When these resolve, React renders and commits each to the DOM. However, only the current selection is displayed, the rest are hidden.

Counter Approach

Another approach is to hide each tab with a Boolean flag:

Hiding tabs using a Boolean flag.

Unfortunately, this actually prevents the tabs from being loaded, rendered and committed to the DOM.

With this implementation, switching tabs triggers the fallback on Suspense, and makes users wait until the lazy loading resolves, it then renders and commits to the DOM.

After that, mount life cycle is executed and data is fetched. Finally, when the network requests resolve, the component is updated, triggering render and commit once again.

Moving away from a tab, will cause the component to be unmounted. If you navigate back, it will need to be mounted, and updated once again. Caching the network response might be helpful here.

To get around these issues, one could pass the hidden flag to the component and return null while it is hidden. This would allow life cycles to occur, but it requires coupling the tab management with your component’s logic.

In summary, the hidden HTML attribute helps you to:

  • Create a better user experience.
  • Reduce the amount of API calls executed.
  • Abstract away tab management logic.
  • Commit work to the Browser, but save resources if its not relevant yet.

The code for this demo lives in this repository.

Happy hacking!

--

--

Joseph Chamochumbi
Evolve Technology

Señor Developer. I do full-stack with JS & TS. Currently mastering Rust.