Blog Hero Background

3 of 3 Derived State


User Avatar
Brad WestfallView My Twitter
May 26, 2021
ReactBeginnerState

Upcoming 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 "sync" our lifted useState state to the URL. I wouldn't do that. This is a form of "state duplication" and is going to lead to bugs. A React developer doing this is probably thinking that useState is the only way to keep track of state over time. I think you should just use the URL state and forget about useState in this case. Have the click events in Sidebar just change the URL. Then use a tool like React Router's useLocation in BrowseProducts to get re-renders when the URL changes.

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
© 2023 ReactTraining.com