15-Dec

React

Compute view logic during render in React

Updating view logic based on some state or props is a common thing to do in a React application, and the go-to method is typically by putting every state update in one or more useEffects. However, this will lead to unnecessary updates and unmaintainable code. Let's take a look at how updating view logic is better by doing it during render rather than useEffects.

3 min read

·

By Caroline Odden

·

December 15, 2022

The content of this article is heavily inspired by the new React docs (which is in beta).

Updating view logic in useEffects

Let's say you have a welcoming message you want to show the user, but you want to have different logic whether the user is logged in or not. The first instinct may be to use useEffects to compute the new data. Let's take a look at the example below:

const [loggedIn, setIsLoggedIn] = useState<boolean>(false)
const [greeting, setGreeting] = useState<string>('You are not logged in')

useEffect(() => {
  const newGreeting = `You are ${isLoggedIn ? '' : 'not'} logged in`
  setGreeting(newGreeting)
}, [isLoggedIn])

Above you have local state keeping track of whether you are logged in or not and state with a greeting you want to show to the user. In addition, you have a useEffect listening on the isLoggedIn state which triggers whenever this value changes, and it will then update the greeting message. This will lead to unnecessary renders because the useEffect depends on other state variables which first will update, then trigger a re-render, then the useEffect will detect the new values in the dependency array and then perform another update.

Updating state variables used for view logic in this way is confusing and hard to read. Using useEffect to update view logic in a component will be slow and trigger unnecessary updates down the tree of components as well. And let's not speak of all the potential bugs that may occur if the state variables get out of sync. Remember that useEffects are for side effects, not maintaining your local state or props.

You don't need useEffects everywhere

The useEffect hook in React is a JavaScript function which is intended to synchronise your effects with the state in your component. Side effects are functions used to communicate with things outside of your component.

Some common examples are data fetching or timing functions like setTimeout and setInterval.

Updating view logic during render

A surprising solution for this issue is to just drop the effect, and rather perform the calculation of the state used for view logic during render. By putting the calculation outside the useEffect the value will be computed at once the value it depends on changes, and it will happen without the re-render as with the useEffect. If we add this solution to the example from above, we get something like this:

const [loggedIn, setIsLoggedIn] = useState<boolean>(false)

const greeting = `You are ${isLoggedIn ? '' : 'not'} logged in`

This will remove unnecessary re-renders in your component and it will be much easier to read for you and other team members. Less code, less error-prone!

Expensive computations

In some cases, the computation can be very expensive, and you don't want to calculate it on every render if nothing has changed. The solution for caching expensive computations is useMemo. Note that the first render will still be expensive, but it will avoid unnecessary updates if nothing has changed based on what's in the dependency array.

An example with useMemo:

const myCostlyComputation = useMemo(() => 
    computeExpensiveCalculation(longList), [longList]
)

To check whether your calculation is expensive I recommend using a proper debugger (React DevTools) or using timing functions to track how much time the calculation takes. Keep in mind that optimization may also be costly, especially if you do it prematurely. I suggest reading this great article about When to useMemo and useCallback.

TL;DR

Drop the useEffects when updating the state used for view logic based on state or props, just calculate it during render 💥.

Up next...

Loading…