DEV Community

David
David

Posted on • Originally published at languageimperfect.com

Avoid This Common React Hook Antipattern

When displaying synchronous data in React components, useState and useEffect are antipatterns that should be avoided. Using useState and useEffect to compute synchronous values will lead to a sub-optimal user experience and subtle bugs that can be hard to debug.

The Problem

Consider the following component:

import React, { useState, useEffect } from 'react';

interface Props {
  firstName: string;
  lastName: string;
}

function MyComponent(props: Props) {
  const { firstName, lastName } = props;
  const [fullName, setFullName] = useState('');

  useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
  }, [firstName, lastName]);

  return (
    <span>{fullName}</span>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example we are computing fullName from two props: firstName and lastName. Even though firstName and lastName are both available at render time, we are passing them both into a useEffect hook. Inside the useEffect hook, we are then concatenating the values and then calling setFullName with the concatenated value.

This is problematic for several reasons:

  • useEffect runs after the component renders and displays a value to the user. This means that the user will always see a stale value before the updated value is shown to the user.
  • To see our computed value, we have to always render our component twice. On the first render, we display a stale value. When the useEffect runs, it calls setFullName which will trigger another render.
  • From a developer perspective, the logic is hard to read and hard to follow.

The fix

The fix for this pattern is simple. Instead of computing the values in a useEffect, we can simply compute the values as part of the "natural" rendering process:

import React from 'react';

interface Props {
  firstName: string;
  lastName: string;
}

function MyComponent(props: Props) {
  const { firstName, lastName } = props;
  const fullName = `${firstName} ${lastName}`;

  return (
    <span>{fullName}</span>
  );
}
Enter fullscreen mode Exit fullscreen mode

Computing the value during render has the following advantages:

  • Our component is much simpler. We were able to completely eliminate any hook usage in our component.
  • The user will never see a stale value. Everytime one of our props changes, it will be immediately reflected in the DOM.
  • We've eliminated an extra render cycle.

The takeaway

When you need to display computed value from synchronous data in React, it is almost always easier and safer to just compute the value in the component's body without useEffect or useState. This will lead to simpler components and a better user experience!

More Reading

Interested to learn about Typescript? Understanding Typescript’s Type Predicates

Top comments (1)

Collapse
 
brense profile image
Rense Bakker

Indeed! But keep in mind that for more complex derived state, you should use the useMemo hook, to prevent slow rendering.

import React from 'react';

type Person = {
  name: string
  isDev: boolean
}

function MyComponent({ people }: { people: Person[] }) {
  const devs = React.useMemo(() => people.filter(person => person.isDev), [people]);

  return (
    <span>{devs.map(dev => dev.name).join()}</span>
  );
}
Enter fullscreen mode Exit fullscreen mode