State
Now that we know how to create HTML elements and components, and how to pass props and event handlers to both using JSX, it's time to learn how to update the Virtual DOM tree.
As we alluded to in the previous chapter, both function and class components
can have state - data stored by a component that is used to change
its Virtual DOM tree. When a component updates its state, Preact re-renders
that component using the updated state value. For function components, this
means Preact will re-invoke the function, whereas for class components it
will only re-invoke the class' render()
method. Let's look at an example
of each.
State in class components
Class components have a state
property, which is an object that holds
data the component can use when its render()
method is called. A component
can call this.setState()
to update its state
property and request that
it be re-rendered by Preact.
class MyButton extends Component {
state = { clicked: false }
handleClick = () => {
this.setState({ clicked: true })
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.clicked ? 'Clicked' : 'No clicks yet'}
</button>
)
}
}
Clicking the button calls this.setState()
, which causes Preact to
call the class' render()
method again. Now that this.state.clicked
is true
, the render()
method returns a Virtual DOM tree containing
the text "Clicked" instead of "No clicks yet", causing Preact to update
the button's text in the DOM.
State in function components using hooks
Function components can have state too! While they don't have a
this.state
property like class components, a tiny add-on module
is included with Preact that provides functions for storing
and working with state inside function components, called "hooks".
Hooks are special functions that can be called from within a function
component. They're special because they remember information across
renders, a bit like properties and methods on a class. For example,
the useState
hook returns an Array containing a value and a "setter"
function that can be called to update that value. When a component is
invoked (re-rendered) multiple times, any useState()
calls it makes
will return the exact same Array each time.
βΉοΈ How do hooks actually work?
Behind the scenes, hook functions like
setState
work by storing data in a sequence of "slots" associated with each component in the Virtual DOM tree. Calling a hook function uses up one slot, and increments an internal "slot number" counter so the next call uses the next slot. Preact resets this counter before invoking each component, so each hook call gets associated with the same slot when a component is rendered multiple times.function User() { const [name, setName] = useState("Bob") // slot 0 const [age, setAge] = useState(42) // slot 1 const [online, setOnline] = useState(true) // slot 2 }
This is called call site ordering, and it's the reason why hooks must always be called in the same order within a component, and cannot be called conditionally or within loops.
Let's see an example of the useState
hook in action:
import { useState } from 'preact/hooks'
const MyButton = () => {
const [clicked, setClicked] = useState(false)
const handleClick = () => {
setClicked(true)
}
return (
<button onClick={handleClick}>
{clicked ? 'Clicked' : 'No clicks yet'}
</button>
)
}
Clicking the button calls setClicked(true)
, which updates the state field
created by our useState()
call, which in turn causes Preact to re-render
this component. When the component is rendered (invoked) a second time,
the value of the clicked
state field will be true
, and the returned
Virtual DOM will have the text "Clicked" instead of "No clicks yet".
This will cause Preact to update the button's text in the DOM.
Try it!
Let's try creating a counter, starting from the code we wrote in the previous
chapter. We'll need to store a count
number in state, and increment its value
by 1
when a button is clicked.
Since we used a function component in the previous chapter, it may be easiest to use hooks, though you can pick whichever method of storing state you prefer.