Before React 16.8, updating state required your component to be class-based. This meant that if you started your component as a “stateless”, or functional component and then realized that you needed state, you’d need to convert your component to be class-based.
With the introduction of React Hooks, this is no longer the case.
The useState hook takes an optional initial state value, and returns two things; the value and a function to update that value. So,
this.state = { count: 0 };
becomes
const [count, updateCount] = React.useState(0);
Notice that this is a familiar JavaScript syntax. The values returned from useState are set as variables (constant variables, lol) with array destructuring; in this case count
is a number (implicitly, because it was initialized with the value 0
), while updateCount
is a function that would be used to update count
.
The example below updates a cart and displays items inside the cart as it is added.
Above, there are 2 state hooks in use; holds an object of an item and that item’s count, while the other holds all items that have ever been added.
Form input handles updating the item by calling updateItem
on onChange
, while the input value is set by directly passing the item
constant — item.name
for the item name input and item.count
for the amount.
On the other hand, submitting the form triggers the logic to add this new item object to an object, that can then be displayed back to the user.
updateCart([ ...cart, { name, count } ])
...cart
spreads the cart
array into this new array because we want to immutably update its value.
Notice that when I called handleUpdateCart
, I passed the item
object, but then where I declared, I destructured the object, exposing the keys to be used directly in the function, instead of having to access them by going item.name
or item.count
. Cool.
Back to updateCart
, { name, count }
is an example of Object Initialization From Variables. This is another JavaScript ES6 shorthand that allows me to set object values because I’m passing a variable that has the same name as the key whose value I’m trying to set.
To display the cart items as a list, cart
is represented as an array, so we have access to JavaScript iterable functions like map
.
const cartHTML = cart.map((item, idx) => (
<tr key={idx}>
<td>{item.name}</td>
<td>{item.count}</td>
</tr>
));
cartHTML
loops through cart
and returns an object that is represented as a table row with cells. Note that in this case, we could also use object destructuring to expand item
to { name, count }
.
const cartHTML = cart.map(({ name, count }, idx) => (
<tr key={idx}>
<td>{name}</td>
<td>{count}</td>
</tr>
));
If you’re familiar with the class-based implementation of updating state, you might remember that state could also be updated based on what it previously was:
this.setState(prevState => {
return { count: prevState.count + 1 }
});
This still works with hooks: