No more unnecessary divs in your React components, use Fragments!

Hey folks, how are you doing?

This time I'm going to talk about a relatively new concept (maybe for you in the future it won't be new anymore) which is React Fragments. But first, let's contextualize the problem that Fragments solve.

PROBLEM #1: It is not possible to return multiple HTML root elements in a React Component.

We learn this in the "kindergarten" of React, but in case you didn't understand what I said, here's an example to explain it better:

function MyComponent() {
  // INVALID!
  // my return statement can have only 1 root HTML element
  // but in this case, I'm returning 2
  return (
    <h1>Root element #1<h1>
    <h1>Root element #2</h1>
  )
}
function MyComponent() {
  // VALID!
  // in this there no problem because we're only returning
  // 1 root element, which is the div
  return (
    <div id="root-element">
      <h1>Element #1<h1>
      <h1>Element #2</h1>
    </div>
  )
}

But why does this happen?

Well, to explain this, we'll have to understand (in a very simplified way) how React, and more specifically JSX, works behind the scenes.

If you have already set up a React application, you probably know that it uses Babel to compile your code, and among the many transformations that Babel makes, one of them is in JSX, where:

// This JSX element
return (
  <h1 className="class-example" id="id-example">
    JSX Element
  </h1>
)

// After Babel's compilation, becomes this:
return React.createElement(
  'h1',
  { className: 'class-example', id: 'id-example' },
  'JSX Element'
)

Note that it transforms JSX into a function called React.createElement, and what it does, as the name implies, is to create an HTML element based on the arguments passed, which are:

  • The HTML element you are creating (h1, div, p, etc.)
  • The attributes (className, id, alt, src, etc.)
  • The text inside of it

So, if you stop to think about it, this function limits us to creating only 1 root element (specified in the 1st argument), and consequently, takes away the possibility of creating multiple root elements inside of it.


Quick pause for some friendly tips:

  • Tip #1: If you want to better understand how Babel works behind the scenes and how it compiles your code (both in the ES6 and JSX part), access the Babel's repl and write some code, it's guaranteed learning!
  • Tip #2: For a more in-depth reading about JSX, I recommend reading this section and this one on the React documentation that talks a little about how JSX works.

Well, now we have a problem, how can we solve it?

If you saw my example above, I released a little spoiler of an element that can help us, which is the div. It gives us the possibility to "encapsulate" the elements we want to return inside it. And this is accepted by Babel, that generates a code like this:

// JSX
return (
  <div className="div-class" id="div-id">
    <h1 id="element-1">Element #1</h1>
    <h1 id="element-2">Element #2</h1>
  </div>
)

// Compiled code
return React.createElement(
  'div',
  { className: 'div-class', id: 'div-id' },
  React.createElement('h1', { id: 'element-1' }, 'Element #1'),
  React.createElement('h1', { id: 'element-2' }, 'Element #2')
)

Now the React.createElement, when receiving the 3rd argument (which is the text) instead of rendering a string, it renders another React.createElement, which will generate the child HTML element(s) inside it. Clever, isn't it? However, this solution brings us another issue.

ps: this not only for div, you can use any HTML element, but the most common ones are those that are used to render other elements inside them (div, section, header, article, etc)

PROBLEM #2: unwanted HTML elements

Maybe you don't want to render a div or a section to encapsulate all the elements you want to create in that component. To contextualize, let's see an example:

function App() {
  return (
    <div className="App">
      <h1>Hello World</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  )
}

Generate this DOM: Generated DOM

Note that we ended up generating one div inside another, bringing an unnecessary level of encapsulation to our application.


And how can we solve this?

Now we've reached the key point of the article. With React 16 (Fiber), we can now render a Fragment (for now, represented as an array of elements) in our Component, as shown in the example:

// Code using Fragment as an Array of element
function MyComponent() {
  return [
    'Text without tags',
    <h1>Text on a h1</h1>,
    <h2>Text on a h2</h2>,
    <p>Text on a p</p>,
  ]
}

Generate this DOM: Generated DOM

Now, instead of returning just one JSX element, we've returned an array of them. Notice that the generated DOM no longer has any "encapsulating" HTML element, only the elements generated by the Array Fragment. This makes it easier to read and also avoids the creation of unnecessary elements.

Wonderful, right? Well, kind of. Using fragments as an array brings up a few more problems:

  • All elements now have to be separated by a comma, bringing a very different pattern from what we know in HTML.
  • All strings must be enclosed in quotes.
  • All elements need to have a key otherwise, you'll get a warning.

I didn't tell you, but my code up there doesn't follow the last rules, so I got the following warning in my console: Error on console: Warning: Each child in a list should have a unique "key" prop. See https://reactjs.org/link/warning-keys for more information.

So, dealing with all these problems can be pretty annoying. But luckily, in React v.16.2.0, a first-class element called React.Fragment was added to the React codebase, and that makes our work much easier when creating fragments.


How does React.Fragment work?

// Example using React.Fragment
import { React } from 'react'

function MyComponent() {
  return (
    <React.Fragment>
      Text without tags
      <h1>Text on a h1</h1>
      <h2>Text on a h2</h2>
      <p>Text on a p</p>
    </React.Fragment>
  )
}
// Example using shorthand notation. Equivalent to React.Fragment
function MyComponent() {
  return (
    <>
      Text without tags
      <h1>Text on a h1</h1>
      <h2>Text on a h2</h2>
      <p>Text on a p</p>
    </>
  )
}

Both components above will generate this DOM: Generated DOM

And that's it! This should work the same way as the previous example using arrays, but without all those complications they bring.


Final considerations

I know this article could have been much simpler and straightforward, but I chose to first bring all this historical context always trying to "problemize" things to show mainly why this feature was created. After all, in the Software Development field, everything moves around creating solutions to solve existing problems, right?

Anyway, I hope you enjoyed my article. Feel free to follow me on my social medias:

See you in the next one. BYE!