React State Management and useState

Account Required

This free lesson is available if you sign in.
If you don't have an account, you can sign up for free.

React State Management and useState

Sign in to access this content

You must sign in to access this content.
If you don't have an account you can sign up for free!

React useState Hook Introduction

A fundamental way to create state within react is with the useState hook. e.g. creating a simple counter:

App.tsx
import { useState } from "react";

export default function App() {
const [count, setCount] = useState(0);
const inc = () => setCount(count + 1);

return (
<>
<div>Count: {count}</div>
<button onClick={inc}>+</button>
</>
);
}

Preventing Stale Values in useState Updates

To ensure that you read the latest value for a particular state, you can pass a callback to the set function for the state.

App.tsx
api.ts
import { useState } from 'react';
import { saveInfo } from './api';

export default function App() {
const [count, setCount] = useState(0);

async function inc() {
await saveInfo();
// setCount(count + 1); // ❌ Bad
setCount(prev => prev + 1); // ✅ Good
}

return (
<>
<div>Count: {count}</div>
<button onClick={inc}>+</button>
</>
);
}

useState lazy initialization

A little known feature of the useState hook is its ability to take a function initializer. This can help improve the performance of your application by allowing you execute the initialization code only once, on initial render of the component.

App.tsx
import { useState } from 'react';

export default function App() {
console.log('render');
const initial = () => {
console.log('initial render');
return +(window.localStorage.getItem('count') ?? 0)
};
const [count, setCount] = useState(initial);

function inc() {
setCount(count => {
const next = count + 1;
window.localStorage.setItem('count', next.toString());
return next;
});
}

return (
<>
<div>Count: {count}</div>
<button onClick={inc}>+</button>
</>
);
}

useState TypeScript annotation

In terms of TypeScript type definitions, the only thing you need to know is that useState takes an optional generic type argument. Most commonly you do not need it as it is inferred automatically from the passed in initial value.

However you can provide it explicitly if you want to ensure that the initial value matches your expectation.

App.tsx
import { useState } from "react";

export default function App() {
// Notice the generic argument `<number>` in `useState<number>(0)`.
const [count, setCount] = useState<number>(0);

function inc() {
setCount((count) => count + 1);
}

return (
<>
<div>Count: {count}</div>
<button onClick={inc}>+</button>
</>
);
}

Introduction to custom React hooks

You can create your own custom use* functions to create custom hooks. These can internally call other hooks like useState.

This allows you to create reusable pieces of logic and reusable components that depend on that logic.

App.tsx
Counter.tsx
import { Counter, useCounter } from "./Counter";

export default function App() {
const first = useCounter();
const second = useCounter();
return (
<>
<Counter {...first} />
<Counter {...second} />

<div>Total: {first.count + second.count}</div>
</>
);
}
javascript
typescript
react
playwright

Enjoy free content straight from your inbox 💌

No spam, unsubscribe at any time.

Transcript

00:00

At the core of React programming is its capability to synchronize business state for logic with a rendered ui. And one of the simplest ways to do this is to define the state for logic with a use state hook. In this lesson, we will take a deep look at this built-in use state hook, some of its little known features like callback updates, lazy initializers, the TypeScript type definitions, and follow this up with a guide to creating your own custom hooks and reusable components. So let's go to demonstrate the use state hook. Consider the simple example of creating a counter component.

00:34

The use state hook is a simple function, which is a part of the core of the react module, which means that we can bring it in with a simple import statement. The use state hook allows us to define anatomic state variable for our components. And hey, we are defining the state which we are initializing to the value zero. And this particular hook function returns an array that contains two members, and the first of these is the current value for this particular state. And the second is a function which can be used to update the state value. It is conventional to store the result into two separate

01:07

variables, which we can do by destructuring the return array of the use state function. Once we have the value and the set value functions, we can use them anywhere within our render function. For example, we can create a new local function called increment, which will in turn invoke the set function by passing in a new count value. And here we are just reading the previous value of the count and incrementing it by one. And then within the return virtual dorm, we can create a simple div that displays the current count value and then a simple button component

01:39

for which we will wire the on click function to the increment utility that we just created. And this simple app demonstrates the basic usage of the use state hook. With the example rendered, we can actually click the button a few times to see the use state magic in action. Let's do a quick recap of what is happening over here. We initialize the state atom with the value of zero. We get back an initial or the last set value along with the set function. We can use the current value whatever we want and we can use the set function to update the current value. And every time we do so,

02:10

the component automatically re renders. Now one thing to be aware of with the use state hook is the potential for stale values. Let's take a look at this problem along with the use state's built-in solution for this issue. Here I've turned my increment function into an async function. This doesn't change any of the existing behavior. So if we click the increment button four times, we do increment the count by four. But what this allows us to do is to actually import an API endpoint. And here I have a simple API called save info. And this can be anything, for example, a backend call

02:43

and as an example, I can invoke save info and make sure that that particular backend call succeeds before I do an increment. Now just like before, when I click the increment button four times, there will still be four calls to the increment function. However, the count only increments by one. And the reason is that after the four save info calls resolve, the set count is actually going to use a stale value of the count, which for all of the functions is actually going to be zero. And all of the four calls to set count set the count to zero plus one, which results in the value of one,

03:18

which is what we are seeing on screen. To get around the issue of a potentially stale read from a previous state, the set count function actually takes a function as its argument and this function gets invoked with the previous value. So you can be guaranteed that you do not use a stale value. And now if you click the button four times, you can see that it works perfectly fine. The four individual calls to save in info resolve and then there are four calls to set count, and each of them will read the last value that was set, which guarantees that whatever the previous value was, that value will get incremented by one.

03:52

A little known feature of the use state hook is its ability to take a function initializer, and this can actually help improve the performance of your application as a use case for lazy initialization and consider the objective of storing and retrieving the state values from local storage without any functional change. At this point, we've taken the initial value and put it into its own local variable called initial. And similarly, before we return the updated count within the increment function, we store it into a temporary variable. This allows us to add an easy intercept before we set the initial value

04:25

to first check if the count value is present within local storage. And if it is, that is what we will return. Otherwise, we initialize it to zero. And similarly, before we set the new count, we will store the updated expected value into local storage so that if the application ever reboots it can access it from the local storage. And now if you increment the count a few times and do hard reload of the application, you can see that the value is still preserved. Now, there is a slight performance hit for using local storage, and it's not particularly ideal

04:56

that every time this particular component renders, it tries to fetch a value from local storage even though it's only relevant for the first initial render because after that, the updated value is what is returned from U State and not the same as this initial value. So whenever something changes within our application that causes a, we will still be reading this local storage dot get item, even though we don't necessarily need to anymore. The solution to this problem is pretty simple. Instead of providing an initial value, we can actually turn the initial into a function. You state takes a lazy initializer function

05:32

and this function is only invoked on the first render. So as the component gets rendered again and again, this initial function does not get invoked anymore, and we can actually prove it quite easily by opening up the developer tools, adding a log statement within our initial function as well as a log statement. Whenever our component gets rendered and now after we do a refresh of the ui, you can see that the render happens and the initial render gets logged out and the initial value is read. But after that, when we interact with the ui, the initial render call no longer gets locked to the screen,

06:06

which means that the initial function is not being invoked and therefore local storage is not being accessed again and again because as you mentioned, we do not need it in terms of type definitions for the U State hook, there really isn't much to it. The only thing that we need to know is that it takes an optional generic type argument. Now, most commonly you do not need it because it is automatically inferred from whatever you passed as the argument to U State, for example, ha passing in a function that returns a number and it automatically infers that the result will be

06:39

of type number and we can verify that when we hover over the count variable. Now, of course, we can provide a custom generic argument in order to enforce what we expect the different types to be. For example, if you wanted it to be a string, we can pass in the type string here. Of course, it's going to error because initial is not returning a string. And of course the right answer in this particular case is to use the generic type number which conforms to our desired initial value. A core part of creating custom components within React is the ability to create reusable pieces of custom logic, and they're actually quite easy to do with custom hooks.

07:15

As a demonstration of this reuse, let's look at the use case of trying to create a reusable counter component. To kick us off, we create a new file specifically for this component, and the first thing that we do is we bring in the use Ted Hook is that is what will be used to build our business logic. Now, it's great to have your components purely presentational. That is they take the props and then simply render it out. And for that purpose, our component will take two things, an initial count value and an incrementing function. And our custom component is simply going to take the two things, the count and the increment,

07:48

and render out a simple virtual dorm with a diff pointing to the count and a button linked to the incrementing function. Now, the key thing to note over here is that we want this reusable piece of logic that gives us discount and increment, and quite simply it's going to be a custom hook which contains the same code that we have seen before where it initializes account by utilizing a use state and then creates an increment function using set count and then simply returns these two things. Congratulations, you've just created your own first custom hook.

08:20

Custom hooks are quite simple. They are just functions which internally use other hooks, and it is conventional to have these functions start with the used prefix, just like the built-in react hooks. Now with this custom component and the custom hook for this particular component created, we can jump back to our app, get rid of the logic that we have there already, as well as the custom rendering wide to that particular logic. And then from our counter module, we will bring in the component as well as the custom hook that we created to help us use that component. The hooks allow us to create reusable pieces of

08:54

Logic for a first and a second counter, and then the component of course allows us to render these pieces of logic to the screen so we can create two simple counter components wired to the first and the second hook results. Now, the reason why we created these reusable pieces of logic separately as hooks is that allows the consuming component additional control. For example, if it wants to display the sum of the first and the second count, it can do that quite easily because the logic is owned by app and not specifically locked into the individual counter components.

09:29

I'll wrap things up there. As always, thank you for joining me and I'll see you in the next one.