Blog Hero Background

3 of 3 Derived State


User Avatar
Brad WestfallSay hi on Twitter
May 26, 2021
ReactBeginnerState

See Our Public Workshops:

3-part Series

The useState and useReducer hooks are React's primitives for making state. However, you shouldn't think of them as being the only way to do state in React (and I'm not talking about global state tools like Redux).

Instead, you should think of state as being any value that can change over time between re-rendering.

For example, the URL can be thought of as "state". The URL is the original "state" for the web. We can subscribe to changes in the URL with tools like React Router.

I've built shopping-cart experiences where you see a list of products similar to Amazon. Then there is a left sidebar where you can filter the list of products by brand, price, type, etc. There is quite a bit of distance in the React component tree between the Sidebar component and the BrowseProducts component so I need to consider where to keep my state so that the Sidebar can update it and the browse page can read it.

Option One: I suppose I could "lift state" to some common ancestor component above both Sidebar and BrowseProducts. It would work but if the user wants to filter by a certain brand, I think it would be unfortunate if they refreshed or shared the URL with a friend and filter state is lost.

Option Two: If we had done this the "old school" way, we would have just tracked a ?brand=xyz" query variable in the URL. Now refreshes would persist the "state" what we want to see between refreshes and shares. So does that mean we duplicate our state with useState and "sync" this lifted useState with the URL? I wouldn't do that. This is a form of state duplication even though we have one lifted useState. It's duplicated because the URL values are also serving as state and now we have to sync them back and fourth. This will be prone to bugs.

New React developers often think that useState is the only way to keep track of state over time. In the shopping cart case, I'd rather choose one or the other instead of duplicating state and it seems the URL is a better choice.

The point is, state in React is not limited to useState and useReducer.

So far though, this isn't what we mean by "derived state" but it's getting us a step closer by thinking outside the useState box.

Derived State

Also sometimes known as "computed state" is when we derive a value for a variable upon re-render from some other state. This is best demonstrated with an example.

Consider this counter that currently does not have derived state:

function Counter() {
const [count, setCount] = useState(0)
function subtract() {
setCount(count - 1)
}
function add() {
setCount(count + 1)
}
return (
<div>
<button onClick={subtract}>Subtract</button>
<div>{count}</div>
<button onClick={add}>Add</button>
</div>
)
}

If I were to want an error message for when the could is below 0, I might do this:

function Counter() {
const [count, setCount] = useState(0)
const [error, setError] = useState('')
function subtract() {
setCount(count - 1)
if (count - 1 < 0) {
setError('Cannot be less than 0')
}
}
function add() {
setCount(count + 1)
if (count + 1 >= 0) {
setError('')
}
}
return (
<div>
<button onClick={subtract}>Subtract</button>
<div>{count}</div>
<button onClick={add}>Add</button>
<p>{error}</p>
</div>
)
}

Now we have more state and we have to "manage" it with each event. It's a simple example but this management could be a lot more involved under other circumstances.

Now let's see what it looks like with derived state instead:

function Counter() {
const [count, setCount] = useState(0)
const error = count < 0 ? 'Cannot be less than 0' : ''
function subtract() {
setCount(count - 1)
}
function add() {
setCount(count + 1)
}
return (
<div>
<button onClick={subtract}>Subtract</button>
<div>{count}</div>
<button onClick={add}>Add</button>
<p>{error}</p>
</div>
)
}

Look at the subtract and add functions! No management needed. We don't really need to explicitly set an error from useState, we can just derive on renders that the error variable is an error string or an empty string. This gives us the exact same behavior in our component but without as much complexity. It's also going to give us less bugs under circumstances where we try to manage state and do it poorly.

Summary

Always derive state where possible. It will make things cleaner, smaller, and easier to re-factor. Only use useState and useReducer if you really do need to explicitly set state and they give you the best option for doing so.


Photo by Zoltan Tasi on Unsplash

Subscribe for updates on public workshops and blog posts

Don't worry, we don't send emails too often.

i love react
© 2024 ReactTraining.com