Run Code in React Before Render

By Dave Ceddia

Want to run some code before your React component renders? There are a few ways to make this work, and we’ll talk about them here.

But, I have to warn you: Running code before render is usually a sign that you’re going against the grain of how React works.

TL;DR – There is no Before Render, only After

It makes perfect sense to think “I want to fetch data before my component renders”. Logical! But not how React works.

Here’s the thing:

React does not wait to render. Ever.

React will gladly kick off an asynchronous data fetch in the background, but then it will immediately proceed with rendering – whether the data has loaded or not. (and you can be almost certain that it will not have loaded yet)

There is no way to make it wait.

All is not lost, though. There’s an easy fix.

Components that render async data need to be prepared to render an empty state, at least once.

Think about what your app should look like before the data is ready. Maybe it’s empty, or maybe it’s a loading spinner, or some fancy skeleton state.

To embrace the way React works, kick off your data fetch after the first render, inside a useEffect block.

Just make sure to initialize the state to something that’s the same type as what it’ll eventually be!

Initialize State Before Render

Initializing state actually does run before the first render, and leaving it uninitialized is a common source of problems.

This leads to errors like Cannot read property 'map' of undefined' when the component tries to render before the data is ready.

If you have a call like useState() with nothing between the parens, that’s uninitialized (it’ll be undefined).

The rule of thumb is to initialize like-with-like: if the state will hold a string, initialize with a string. If it’s a number, init with a number. And so on.

Initialize Arrays

If you’re expecting an array from the server, initialize with an empty array.

const [items, setItems] = useState([]);

Initialize Objects

If you’re expecting an object, init with an object, or maybe null.

const [user, setUser] = useState(null);

Initialize State Lazily

If your init code has to do some heavy work, like mapping/filtering/reducing an array, you can wrap that initialization in a function and it will only run once:

const [products, setProducts] = useState(() => {
  return hugeListOfProducts.filter(isOnSale);
})

This is not a good place to fetch data or do anything asynchronous, though. Put async actions in a useEffect.

What Will Happen Before the Data is Ready?

Look through your code and make sure it won’t blow up if the data isn’t ready (if the value is null). Be especially careful if the data is initialized to or can become null or undefined!

return (
  <div>
    {user && user.name ? user.name : "Not loaded yet"}
  </div>
)

There are two new operators in ES2020 that can make this code simpler: optional chaining (?.) and nullish coalescing (??).

The optional chaining operator (?.) lets you safely access properties of an object that could be null.

return (
  <div>
    {user?.name || "Not loaded yet"}
  </div>
)

The nullish coalescing operator (??) returns the right-hand side when the left side is null or undefined. It’s useful in cases where you might normally use ||, like this:

return (
  <div>
    {user?.commentCount || "Not loaded yet"}
  </div>
)

This example has a bug – it will show “Not loaded yet” when commentCount is 0. Using the ?? operator instead of ||, it’ll work correctly:

return (
  <div>
    {user?.commentCount ?? "Not loaded yet"}
  </div>
)

?? works like the OR || operator, except that it doesn’t consider 0, '' or false to be falsy.

Fetch Data Before Render in the Parent

If you absolutely need to run some code before a component renders, then the solution is to avoid rendering that component at all, until you’re ready.

That means conditionally rendering it in the parent, which would look something like this. More detail in the comments:

function Child({ items }) {
  // Problem:
  // This will error if `items` is null/undefined
  return (
    <>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </>
  );
}

function Parent() {
  // Uninitialized state will cause Child to error out
  const [items, setItems] = useState();

  // Data does't start loading
  // until *after* Parent is mounted
  useEffect(() => {
    fetch('/data')
      .then(res => res.json())
      .then(data => setItems(data));
  }, []);

  // Solution:
  // don't render Child until `items` is ready!
  return (
    <div>
      {items && <Child items={items}/>}
    </div>
  );
}

That’s It!

I hope that helps clear up some confusion around how to do things before mounting a React component. Just remember: There is no before, only after.

For a great deep dive into how React renders and re-renders, check out Mark Erikson’s guide to React rendering behavior.