The most common problem in React applications
June 22, 2021
Originally published to dev.to
I started playing with React about 5 years ago. The most common problem I've seen in React applications 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 thefiltered
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 thefiltered
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.