Learn how to use the useState and useEffect hooks to create dynamic interactions in React
React is designed to build dynamic apps with lots of interaction. A common difficulty with apps like this is keeping the DOM up-to-date as the user interacts. React has two concepts to help keep this manageable: "state" and "effects".
State is data that changes while your application is running. This might be in response to user actions, or after a fetch
request finishes.
In React all stateful values are stored in JS as special variables. We can render our UI based on these variables—when they change React will automatically re-run the component function and update the DOM to reflect the new state value.
Imagine we have a counter component. When the button is clicked we want the count to go up one:
We need some way to make our Counter
function run again if this value changes.
The React.useState
method can be used to create a "stateful" value. It takes the initial state value as an argument, and returns an array. This array contains two things: the state value, and a function that lets you update the state value.
It's common to use array destructuring to simplify this:
The setCount
function lets us update our state value and tells React to re-run this component. E.g. if we called setCount(10)
React will call our Counter
component function again, but this time the count
variable would be 10
instead of 0
.
This is how React keeps your UI in sync with the state.
We have a function that will let us update the state, but how do we attach event listeners to our DOM nodes?
You can pass event listener functions in JSX like any other property. They are always formatted as "on" followed by the camelCased event name. So "onClick", "onKeyDown", "onChange" etc.
In this example we are passing a function that calls setCount
with our new value of count
.
Time to add some state! Open up challenge-1.html
in your editor. You should see the Counter
component we just created. This is an example; you can delete it if you want.
Create a new component called Toggle
. It should render a button that toggles a boolean state value when clicked. It should also render a div containing its children, but only when the boolean state value is true.
Example usage:
React is designed to make it easy to keep your application in sync with your data/state. Component functions render DOM elements and keep them in sync with any state values.
But most apps need more than just a UI—there are also things like fetching data from an API, timers/intervals, global event listeners etc. These are known as "side effects"—they can't be represented with JSX.
We need a way to ensure our effects reflect changes in state just like our UI does.
React provides another "hook" like useState()
for running side-effects after your component renders. It's called useEffect()
. It takes a function as an argument, which will be run after every render (by default).
Let's say we want our counter component to also update the page title (so the count shows in the browser tab). There's no way to represent this update using the JSX our component returns. Instead we can use an effect:
React will run the arrow function we passed to useEffect()
every time this component renders. Since calling setCount
will trigger a re-render (as the state is updated) the page title will stay in sync with our state as the button is clicked.
By default all the effects in a component will re-run after every render of that component. This ensures the effect always has the correct state values.
If your effect does something expensive/slow like fetching from an API (or sorting a massive array etc) then this could be a problem.
useEffect()
takes a second argument to optimise when it re-runs: an array of dependencies for the effect. Any variable used inside your effect function should go into this array:
Now our effect will only re-run if the value of count
has changed.
Sometimes your effect will not be dependent on any props or state, and you only want it to run once (after the component renders the first time). In this case you can pass an empty array as the second argument to useEffect()
, to signify that the effect has no dependencies and never needs to be re-run.
For example if we wanted our counter to increment when the "up" arrow key is pressed:
We add an event listener to the window
, and pass an empty array to useEffect()
. This will keep us from adding new event listeners every time count
updates and triggers a re-render.
Some effects need to be "cleaned up" if the component is removed from the page. For example timers need to be cancelled and global event listeners need to be removed. Otherwise you'd have a bunch of code running in the background trying to update a component that doesn't exist anymore.
If you return a function from your effect React will save it and call it if the component is removed from the page. React will also run it to clean up when a component re-renders (before the effects run again).
Let's clean up after our effect example from above:
The cleanup
function we return will be called if the component unmounts (is removed from the page). That will ensure we don't keep running an unnecessary event listener and trying to update state that doesn't exist anymore.
We're going to enhance our Toggle
component from Part 3. You can either keep working in the same file or open up challenge-2.html
to start fresh.
Edit the Toggle component so that the page title (in the tab) shows whether the toggle is on or off.
Then create a new component called MousePosition
. It should keep track of where the mouse is in the window and render the mouse x and y positions.
Put MousePosition
inside your Toggle
so you can show and hide it. This is how your final App
should look: