Skip to content

Flow Control in React


Learning how to do JSX with React can be really fun for those who are starting out (once you've mastered some of the confusing parts. It can feel empowering to create abstractions and re-usable components. The most common abstraction that I've seen beginners make can be categorized as "flow-control". In other words, creating an <If> or <Loop> component:

<div>
  <If condition={results}>
    <UserList users={results} />
  </If>
</div>

At first this seems really nice. After all, who wouldn't understand what's going on? But then you decide you need to have an else condition as well, so you start off like this:

<div>
  <If condition={results}>
    <UserList users={results} />
  </If>
  <If condition={!results}>
    <Loading />
  </If>
</div>

It works, but it's starting to bug you a little because it would be much nicer if we make an <Else> component. So you do some research and you might eventually read about Compound Components and figure out how to do this:

<div>
  <If condition={results}>
    <Then>
      <UserList users={results} />
    </Then>
    <Else>
      <Loading />
    </Else>
  </If>
</div>

At some point you start to think about more edge cases so you deal with those as they come, but eventually what you realize is that you've started to create a DSL (Domain Specific Language).

If you have not heard this term before, a DSL is somewhat of a pseudo language that's most often used in templating for conditionals and flow. They can be considered a sub-language within another "host" language. Common examples would be older tools like Mustache and Handlebars, and even some modern tools like Angular (where the host language is JavaScript).

The problem with creating your own flow-control DSL in React is that:

  1. It's not necessary because React lets you use plain JavaScript for flow-control and a DSL is never going to be more capable or more performant than the host language. The only thing it can hope for is to be nicer syntax.
  2. It's not great for the React community to have dozens of different flow-control abstractions that differ in API from each other such that each one has to be learned separately. There's a lot of value in having the community do flow control with one syntax (which takes us back to point 1).
  3. They can be more error prone. Consider this code:
const list = null
return (
  <If condition={Array.isArray(list)}>
    <span>{list[0]}</span>
  </If>
)

The real conditional logic is inside the If component where condition and children are received as props and evaluated. But JSX is actually going to evaluate the {list[0]} part before it's sent in which means we're treating null as if it were an array which will cause an error (regardless of what we pass into condition)

Doing it in JavaScript is less error prone in this case:

const list = null
return Array.isArray(list) && <span>{list[0]}</span>

JSX feels limited, at first

While learning React, we quickly learn that we can do JavaScript inside of JSX using curly braces but it's limited to expressions and operators that yield a value. Why is this a limitation?

Keep in mind that the React API actually doesn't understand JSX. It is apart of React in the sense that the React team created it for React. But JSX is meant to be compiled by something like Babel in a build step:

// When we write this JSX
<div title={ourTitle}>Hello World</div>

// Babel will rewrite it to this in the build files.
React.createElement('div', { title: ourTitle }, 'Hello World')

In JSX, doing title={ourTitle} means that ourTitle will become the value of an object. This is the reason why you must only do JavaScript that resolves to a value.

You can either choose to write your React with React.createElement or you can use JSX as long as you use Babel to compile it into React.createElement. Either way, React only understands React.createElement.

Ternaries and Short Circuiting with &&

Typically those who are experimenting with custom flow-control components are well aware using ternaries and && for conditionals:

// The first <If> example above could have been a ternary
<div>
  {results
    ? <UserList users={results} />
    : null
  }
</div>

// Or it could make use of && 
<div>
  {results && <UserList users={results} />}
</div>

The benefit of using ordinary JavaScript for flow-control is that it's apart of the actual language so more people will understand it and also it won't vary from project to project. However, it starts to seem like we might run into another limitation of JSX -- what if we need two sibling JSX nodes returned from the condition like this?

// Error: JSX expressions must have one parent element.
<div>
  {results && (
    <UserList users={results} />
    <Pagination />
  )}
</div>

JSX doesn't allow us to do this because it needs one JSX wrapping element for our UserList and Pagination. This might seem confusing because it looks like the div is the wrapping element, but since the two custom elements are siblings in a the curly brace section, we need a wrapper directly around them. We could arbitrarily wrap them in a div or span, but that could have styling side-effects we don't want.

Perhaps this is one of the motivations behind making <If>. Since <If> is a component, we can have two sibling children like this:

<div>
  <If condition={results}>
    <UserList users={results} />
    <Pagination />
  </If>
</div>

If this were the motivation, then just keep in mind that JSX Fragments were created to solve this type of problem. We can just use JSX and normal JavaScript to avoid making our own flow-controls:

<div>
  {results && (
    <Fragment>
      <UserList users={results} />
      <Pagination />
    </Fragment>
  )}
</div>

Nested Ternaries can be Ugly

Different developers have different thresholds for what they consider to be readable code. Some devs prefer to never do nested ternaries because even the most simple ones can be ugly:

<div>
  {!results
    ? <Loading />
    : authenticated
      ? <UserList users={results} />
      : <LoginForm />
  }
</div>

This is possibly another motivation for custom flow-control components. Just keep in mind that you don't have to use nested ternaries if you don't want to. Here's how we could get the same logic:

<div>
  {!results && <Loading />}
  {results && authenticated && <UserList users={results} />}
  {results && !authenticated && <LoginForm />}
</div>

This can also be a good way to achieve "switch-statement" types of flow if you need it.

It Takes some Getting Used To

I think the most common criticism of using plain JavaScript to do control flow is that it feels ugly at first. This is something that usually goes away when you embrace it and read other's code more. It actually starts to feel really nice and powerful that you have all of JavaScript at your disposal in JSX.

Loading...

React Router

Michael Jackson and Ryan Florence create the React libraries that you use in your apps like React Router and Reach UI. All of our trainers are experts in React and JavaScript so let us share our knowledge with you and your team!

I Love React
© React Training 2019