React hooks tutorial for beginners
August 17, 2019
I’m pretty new to React hooks, too, so I don’t know enough to confuse you or get too deep.
Let’s get started
Initialize a new app with create-react-app.
npx create-react-app react-hooks-tutorial
Not familiar with npx? Read this.
Enter the project’s directory and fire up the app.
cd react-hooks-tutorial
npm start
If you get errors about permissions, you might need to use
sudo
.
You also might need to chown the app directory.
This tutorial will use React hooks to manipulate the spinning React logo and other aspects of the boilerplate App.js
.
Change the spin direction with the useState
hook
Let’s use the most simple hook useState
to toggle the direction of the React logo between clockwise and
counterclockwise.
First, import the hook.
import React, { useState } from "react";
Next we create the hook, which declares a state variable spinClockwise
and initializes its value to be true
. As part
of the hook declaration,
React creates a method to update the new state variable.
const [spinClockwise, setSpinDirection] = useState(true);
Update the React logo’s animationDirection
to be set based on our component’s state.
<img
src={logo}
className="App-logo"
alt="logo"
style={{ animationDirection: spinClockwise ? "normal" : "reverse" }}
/>
Below the img
, add a button that toggles the spin direction.
<button onClick={() => setSpinDirection(!spinClockwise)}>
{spinClockwise ? "Switch to counterclockwise" : "Switch to clockwise"}
</button>
Here’s how your App.js
should look at this point.
import React, { useState } from "react";
import logo from "./logo.svg";
import "./App.css";
function App() {
const [spinClockwise, setSpinDirection] = useState(true);
return (
<div className="App">
<header className="App-header">
<img
src={logo}
className="App-logo"
alt="logo"
style={{ animationDirection: spinClockwise ? "normal" : "reverse" }}
/>
<button onClick={() => setSpinDirection(!spinClockwise)}>
{spinClockwise ? "Switch to counterclockwise" : "Switch to clockwise"}
</button>
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
Click the button and see the React logo spin in different directions. That’s our first hook in action!
Learn more about useState
here.
Customize the browser page title with the useEffect
hook
Gotta import the hook.
import React, { useState, useEffect } from "react";
In the component, but outside the return statement, add the hook.
useEffect(() => {
document.title = "React Hooks Tutorial";
}, []);
Check out your updated browser page title to make sure the hook worked.
What’s going on here?
Here’s the explanation of the useEffect
hook from the React docs
The Effect Hook,
useEffect
, adds the ability to perform side effects from a function component. It serves the same purpose ascomponentDidMount
,componentDidUpdate
, andcomponentWillUnmount
in React classes, but unified into a single API.
So the useEffect
hook takes a function as the first argument, which is executed after every completed render, unless
it’s
provided a second argument. The optional second argument is an array of values that the effect depends on. If it’s an
empty array, then the effect will only be run once, when the component mounts. It’s basically componentDidMount
.
Learn more about useEffect
here.
Tweak the “Learn React” link with useContext
You know the drill.
import React, { useState, useEffect, useContext } from "react";
Outside of theApp
functional component, but in the same file, declare a new context like the code below.
const ExampleContext = React.createContext({
text: "Learn React Hooks",
href: "https://reactjs.org/docs/hooks-intro.html",
});
Now inside our component, get the ExampleContext
’s value with the useContext
hook, which takes the whole
context object as an argument.
const contextValue = useContext(ExampleContext);
Now we’ll use the contextValue
to set a new link that’s more specific about learning React hooks.
<a
className="App-link"
href={contextValue.href}
target="_blank"
rel="noopener noreferrer"
>
{contextValue.text}
</a>
Here’s how your App.js
should look at this point.
import React, { useState, useEffect, useContext } from "react";
import logo from "./logo.svg";
import "./App.css";
const ExampleContext = React.createContext({
text: "Learn React Hooks",
href: "https://reactjs.org/docs/hooks-intro.html",
});
function App() {
const [spinClockwise, setSpinDirection] = useState(true);
const contextValue = useContext(ExampleContext);
useEffect(() => {
document.title = "React Hooks Tutorial";
}, []);
return (
<div className="App">
<header className="App-header">
<img
src={logo}
className="App-logo"
alt="logo"
style={{ animationDirection: spinClockwise ? "normal" : "reverse" }}
/>
<button onClick={() => setSpinDirection(!spinClockwise)}>
{spinClockwise ? "Switch to counterclockwise" : "Switch to clockwise"}
</button>
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href={contextValue.href}
target="_blank"
rel="noopener noreferrer"
>
{contextValue.text}
</a>
</header>
</div>
);
}
export default App;
Learn more about useContext
here.
Change the speed of the logo’s spin with useReducer
If you aren’t familiar with flux patterns or haven’t used Redux before, then this can look pretty weird and confusing
. You might want learn the concept behind useReducer
before looking at this example.
import React, { useState, useEffect, useContext, useReducer } from "react";
Define a new reducer function (outside the component function) with one piece of state, spinDuration
(in seconds).
const initialState = { spinDuration: 2 };
function reducer(state, action) {
switch (action.type) {
case "increaseSpeed":
return { spinDuration: state.spinDuration - 0.2 };
case "decreaseSpeed":
return { spinDuration: state.spinDuration + 0.2 };
default:
return state;
}
}
When we want to increaseSpeed
of the logo, we have to decrease the spin duration of the logo. That’s just how the
animation-duration
css property works, which we’ll add to the img
soon.
Now, under where we put useContext
, let’s create our reducer’s state
and dispatch
.
const [state, dispatch] = useReducer(reducer, initialState);
state
is an object that we initialized withinitialState
that has the current state property values.dispatch
is a function that sends objects (usually referred to as actions) with atype
property, so the reducer knows what update needs to be made tostate
.
To actually dispatch actions and change the speed of the spinning logo, create two buttons (below the one to toggle spin direction).
<button
disabled={state.spinDuration <= 0.4}
onClick={() => dispatch({ type: "increaseSpeed" })}
>
Increase speed
</button>
<button
disabled={state.spinDuration >= 3}
onClick={() => dispatch({ type: "decreaseSpeed" })}
>
Decrease speed
</button>
Finally, update the logo img
to have it’s animation-duration
tied to the state
.
<img
src={logo}
className="App-logo"
alt="logo"
style={{
animationDirection: spinClockwise ? "normal" : "reverse",
animationDuration: `${state.spinDuration}s`,
}}
/>
Mess around with the buttons and see the logo spin slower and faster. That’s all being done with useReducer
.
Efficiently re-render the spin direction button with useMemo
This hook helps you take advantage of a performance optimization technique called memoization.
useCallback
does something similar, but returns a memoized callback function, rather than a value.
From Wikipedia…
In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
In the context of React and useMemo
, rather than recalculate values, re-run functions or rebuild content every time
our component re-renders, we can memoize them, a.k.a. make them rerun only when certain values update.
First, let’s refactor our spin direction button to be returned by a simple render function.
// ... no changes
const renderSpinDirectionButton = () => {
console.log("RENDER BUTTON");
return (
<button onClick={() => setSpinDirection(!spinClockwise)}>
{spinClockwise ? "Switch to counterclockwise" : "Switch to clockwise"}
</button>
);
};
return (
<div className="App">
<header className="App-header">
<img
src={logo}
className="App-logo"
alt="logo"
style={{
animationDirection: spinClockwise ? "normal" : "reverse",
animationDuration: `${state.spinDuration}s`
}}
/>
{renderSpinDirectionButton()}
// ... no changes
All we did was move the original button into a render function, and added a console.log
to see how frequently that
button re-renders.
Open up your browser console
and notice how often that button re-renders when it doesn’t need to change,
particularly when increasing and decreasing the spin speed. Ideally, that button would only re-render when it’s
clicked, so that the button text can update.
Obviously re-rendering that button isn’t in need of memoization for performance optimization, but it works as an example.
We only need to two things to the renderSpinDirectionButton
function to memoize it, a.k.a. make it only
re-render when the spinClockwise
state variable changes.
- Pass the function into
useMemo
as the first argument. - Provide an array of dependencies as the second argument in
useMemo
.
Here’s the updated code. Note that we only re-render the button when spinClockwise
updates, and that
renderSpinDirectionButton
is now the returned value of the memoized function, rather than a function to be executed.
import React, { useState, useEffect, useContext, useReducer, useMemo } from "react";
// ... no changes
const renderSpinDirectionButton = useMemo(
() => {
console.log("RENDER BUTTON");
return (
<button onClick={() => setSpinDirection(!spinClockwise)}>
{spinClockwise ? "Switch to counterclockwise" : "Switch to clockwise"}
</button>
);
},
[spinClockwise]
);
return (
// ... no changes
{renderSpinDirectionButton}
// ... no changes
Check out the browser console now, and notice how the button re-renders when it’s clicked, a.k.a. when the
spinClockwise
variable changes. That’s memoization doing its job.
And that’s it!
You now know how to implement the basics of React’s most useful hooks.
Here’s the final App.js
import React, {
useState,
useEffect,
useContext,
useReducer,
useMemo,
} from "react";
import logo from "./logo.svg";
import "./App.css";
const ExampleContext = React.createContext({
text: "Learn React Hooks",
href: "https://reactjs.org/docs/hooks-intro.html",
});
const initialState = { spinDuration: 2 };
function reducer(state, action) {
switch (action.type) {
case "increaseSpeed":
return { spinDuration: state.spinDuration - 0.2 };
case "decreaseSpeed":
return { spinDuration: state.spinDuration + 0.2 };
default:
return state;
}
}
function App() {
const [spinClockwise, setSpinDirection] = useState(true);
const contextValue = useContext(ExampleContext);
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
document.title = "React Hooks Tutorial";
}, []);
const renderSpinDirectionButton = useMemo(() => {
console.log("RENDER BUTTON");
return (
<button onClick={() => setSpinDirection(!spinClockwise)}>
{spinClockwise ? "Switch to counterclockwise" : "Switch to clockwise"}
</button>
);
}, [spinClockwise]);
return (
<div className="App">
<header className="App-header">
<img
src={logo}
className="App-logo"
alt="logo"
style={{
animationDirection: spinClockwise ? "normal" : "reverse",
animationDuration: `${state.spinDuration}s`,
}}
/>
{renderSpinDirectionButton}
<button
disabled={state.spinDuration <= 0.4}
onClick={() => dispatch({ type: "increaseSpeed" })}
>
Increase speed
</button>
<button
disabled={state.spinDuration >= 3}
onClick={() => dispatch({ type: "decreaseSpeed" })}
>
Decrease speed
</button>
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href={contextValue.href}
target="_blank"
rel="noopener noreferrer"
>
{contextValue.text}
</a>
</header>
</div>
);
}
export default App;
Hi, I'm Ryan. I live in Denver and work remotely as a JavaScript/React/Node Developer. I'm always having fun building side projects and sometimes write JavaScript-related tutorials that help folks build things, too.