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-tutorialNot familiar with npx? Read this.
Enter the project’s directory and fire up the app.
cd react-hooks-tutorial
npm startIf 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, andcomponentWillUnmountin 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);stateis an object that we initialized withinitialStatethat has the current state property values.dispatchis a function that sends objects (usually referred to as actions) with atypeproperty, 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 changesAll 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
useMemoas 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 changesCheck 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.