The most common problem in React code

I started playing with React about 5 years ago. The most common problem I've seen in React code is duplicate state.

Duplicate state always leads to problems.

So what do I mean with duplicate state?

Let's say we have a component that displays blogposts:

function Blogposts({ blogposts }) {
  return <ul>{blogposts.map(blogpost => ...)}</ul>
}

If we want to add a search to this list of blogposts:

function Blogposts({ blogposts }) {
  const [filtered, setFiltered] = useState(blogposts)
  const [search, setSearch] = useState("")

  return (
    <div>
      <input
        type="text"
        onChange={e => {
          setSearch(e.target.value)
          setFiltered(
            blogposts.filter(
              blogpost => e.target.value === "" || blogpost.title.includes(e.target.value))
            )
          )
        }}
        value={search}
        placeholder="Search"
      />
      <ul>{filtered.map(blogpost => ...)}</ul>
    </div>
  )
}

Note: We want to show all blogposts if the search is empty (e.target.value === "")

This will work but there are some flaws with this approach:

  • If blogposts changes, we'll need to make sure that the filtered list is updated
  • If we want to persist the search parameter across pages (e.g. using a query parameter) we'll need to make sure the filtered list is initialised correctly
  • The component is hard to reason about

We'll have to make sure filtered is always up-to-date.

This becomes a lot harder with bigger components.

How can we fix this?

In this case we can calculate the filtered list if we have blogposts and search:

function Blogposts({ blogposts }) {
  const [search, setSearch] = useState("")

  return (
    <div>
      <input
        type="text"
        onChange={e => setSearch(e.target.value)}
        value={search}
        placeholder="Search"
      />
      <ul>
       {blogposts
         .filter(
           blogpost => search === "" || blogpost.title.includes(search)
         )
         .map(blogpost => ...)
       }
      </ul>
    </div>
  )
}

We calculate the filtered list as part of the render cycle of the component.

Whenever the state changes, the component will re-render.

This means that we no longer have to keep filtered up-to-date:

  • We free our minds from having to think about filtered
  • The filtered list will always be correct
  • The component is easier to reason about

So here's my personal rule:

Always derive from the state if possible.

What about performance?

In most cases it's negligible because JavaScript is fast (unless you're computing heavy stuff).

You can use useMemo if you need too.

Let me know if this blogpost was helpful! 😊

21