The blog ofJonathan Pepin

React anti patterns

2019-02-03

Dan Abramov recently wrote a very nice article going deep into how React works, looking at it as a Runtime. It explains very clearly how things such as the virtual DOM and rendering work, and I can't recommend it enough.

It made me realise that I was writing a few anti-patterns because of a lack of understanding of a few fundamental things, so I took some notes and thought I'd share!

Conditionals

Avoid inline conditionals.

See what I often do:

  function Input({error}) {
    return (
      <div className="inputWithError">
        {
          error && 
          <div className="error">{error.msg}</div>
        }
        <input />
      </div>
    )
  }

The problem here is that when there are no errors, only the input will get rendered. That means that the node "inputWithError" will have only 1 children, the input element.

When an error occurs and React updates its virtualDOM, it will now render an "inputWithError" div with 2 children and won't be able to see that the input element is the same, having only changed position. It doesn't have to render it, but can't know that.

Instead, use conditionals outside of the render method, so a null node is still rendered, keeping the input always in 2nd position, even when there are no errors.

  function Input({error} {
    let errorDiv = null
    if (error) {
      errorDiv = <div className="error">{error.msg}</div>
    }
    return (
      <div className="inputWithError">
        {error}
        <input />
      </div>
    )
  }

Give relevant key props

When working with lists, React will raise a warning telling you that each element of a list needs to have a unique key to identify them.

Here's the usual anti pattern:

  function ShoppingList({list}) {
    const items = []
    for (let i = o; i < list.length; i++) {
      const item = list[i]
      items.push(
        <p key={i}>You bought {item.name}</p>
      )
    }
    return (
      <form>
        {items}
      </form>
    )
  }

This pretty much makes the use of key useless. The key here is used by React to know if an item is new or if it changed position. If the key is the actual position, React will always think that the item is the same.

Instead, use map and meaningful keys:

  function ShoppingList({ list }) {
    return (
      <form>
        {list.map(item => (
          <p key={item.productId}>
            You bought {item.name}
            <br />
            Enter how many do you want: <input />
          </p>
        ))}
      </form>
    )
  }

Batch rendering

A question I often asked myself is what really happens when multiple state updates happen.

This happens often in bigger apps, where an event usually triggers multiple actions (for example with Redux, multiple reducers listening to the same action).

React is smart enough to batch updates inside event handlers, triggering a render only once despite the state being updated multiple times.

From this example:

  function Parent() {
    let [count, setCount] = useState(0);
    return (
      <div onClick={() => setCount(count + 1)}>
        Parent clicked {count} times
        <Child />
      </div>
    );
  }

  function Child() {
    let [count, setCount] = useState(0);
    return (
      <button onClick={() => setCount(count + 1)}>
        Child clicked {count} times
      </button>
    );
  }

Here's what would happen:

  *** Entering React's browser click event handler ***
  Child (onClick)
    - setState
  Parent (onClick)
    - setState
  *** Processing state updates                     ***
    - re-render Parent
    - re-render Child
  *** Exiting React's browser click event handler  ***