Intro to React Hooks - useState
For stateful and side-effect functionality, React provides what are known as hooks. These are JavaScript functions typically imported from React, defined in your own files, or imported from external packages. Hooks must begin with use
in order to distinguish from other JS functions.
Hooks can only be called inside your functional React components. "Calling a hook" just means running the function, typically with initial or default values. The hooks can return anything or nothing, there are no hard rules about what a hook is supposed to do- but there are rules about when and where you can call a hook.
The most common hook is useState
. We will demonstrate this hook with a simple counter example, which is also used in React's useState docs. The useState
hook takes in an initial state value. In our example, this is an intial count value.
useState
returns an array- the first element in the array is the current value of the state. On the first render, the current state value is equal to the initial state value passed to the hook. The second value in the array is the setter. This allows you to set the new state value. In our example, this is setCount
. The notation of const [count, setCount]
is using destructuring to name the array values, since array values do not have variable names.
The setter (also known as setState
) can accept the new current value, or a function. In our example, we demonstrate increment
implementations with the new value, and function. The functional implementation should only be used if you need to compute the new state based on the previous state value. In most cases, you will have the current state value in scope, so you usually don't need a function.
An example of when you would need a function is if you need to perform multiple state updates in a row that depend on each other, and so in that case, using the current value of the state would not be up to date in the second setState
call, because the first call hasn't caused a rerender yet.
Calling the setState
will trigger a rerender on the component, but only if the value is different than the previous render.
setState object example
We also demonstrate an object example of terminating and hiring a user by setting a boolean value inside of an object. By using the object spread syntax, we call setUser
with the current user spread ...user
over a new object, and then isEmployed
with the new boolean value determined based on the current boolean value. You cannot simply call setUser({isEmployed: true})
because this would set the entire new object and exclude the User's name. So, we use the spread syntax to insert all of the current user's keys and values into the new object, and then by adding the isEmployed
key and value, we overwrite that value in the object (since the user would contain the old key / value). Because objects are built by insertion order, the last key / value of isEmployed
would take precedence.
import { useState } from "react"; const INITIAL_COUNT = 0; const INITIAL_USER = { name: "Bob", isEmployed: true }; function CounterExample() { const [count, setCount] = useState(INITIAL_COUNT); return ( <> <h1> Counter example </h1> <h3> Count: {count}</h3> <button onClick={() => setCount(count + 1)}>Increment</button> <button onClick={() => setCount((prevCount) => prevCount + 1)}> Increment (with function) </button> <button onClick={() => setCount(count - 1)}>Decrement</button> </> ); } function ObjectExample() { const [user, setUser] = useState(INITIAL_USER); const { name, isEmployed } = user; return ( <> <h1> Object example </h1> <h3> {name} is {isEmployed ? "employed" : "not employed"} </h3> <button onClick={() => setUser({ ...user, isEmployed: isEmployed ? false : true }) } > {isEmployed ? "Terminate" : "Hire"} </button> </> ); } export default function App() { return ( <div> <CounterExample /> <ObjectExample /> </div> ); }