React Hooks for Beginners

Sep 14, 2021

Hooks introduce a new way to create stateful components in React. Since these components manage their internal states, component composition and testing become significantly easier.

Another exciting feature coming with React hooks is simplified lifecycle methods. As React hooks can be used multiple times in a component, we can implement independent code blocks to handle separate concerns.

In this guide, we will go over the most common React hooks and their use cases.

Rules

Before exploring React hooks, let's review two main rules of hooks:

  1. Hooks should be called inside function components and custom hooks only
  2. Hooks should be placed at the top level only

These rules are enforced by some implementation details, luckily they are easy to remember.

useState

useState hook converts plain JavaScript functions into stateful React components with the following signature:

const [<state value>, <state setter>] = useState(<initial value>)

We created a cool library list in React for Beginners guide. CoolLibrary has a state named likes for the number of likes. Let's add another state for the number of unlikes.

function CoolLibrary({ name }) {
// create local states for number of likes and unlikes
const [likes, setLikes] = React.useState(0);
const [unlikes, setUnlikes] = React.useState(0);
// handle button click for like
function handleLikeClick() {
setLikes(likes + 1);
}
// handle button click for unlike
function handleUnlikeClick() {
setUnlikes(unlikes + 1);
}
return (
<p>
{name} has {likes} likes and {unlikes} unlikes!{" "}
<button onClick={handleLikeClick}>Like</button>{" "}
<button onClick={handleUnlikeClick}>Unlike</button>
</p>
);
}
function LibraryList({ libraries }) {
return (
<div>
{libraries.map((library) => (
<CoolLibrary name={library} />
))}
</div>
);
}
ReactDOM.render(
<LibraryList
libraries={["React", "Vue.js", "Angular", "Svelte", "Ember.js"]}
/>,
document.getElementById("root")
);

As we can see from the sample code, each useState call creates an independent state variable and its setter function. We can use setLikes and setUnlikes to update likes and unlikes respectively.

useEffect

useEffect hook performs side effects with the following signature:

useEffect(<callback>, <optional dependencies>);

Defining side effects can be a little ambiguous, so let's learn useEffect through examples.

Until now, our CoolLibrary component displayed only local states (likes and unlikes). What would happen if we wanted to show GitHub stars for each library? We would need to get the data from GitHub API.

function CoolLibrary({ name, repository }) {
// create local states
const [likes, setLikes] = React.useState(0);
const [unlikes, setUnlikes] = React.useState(0);
const [stars, setStars] = React.useState(0);
// handle button click for like
function handleLikeClick() {
setLikes(likes + 1);
}
// handle button click for unlike
function handleUnlikeClick() {
setUnlikes(unlikes + 1);
}
React.useEffect(() => {
// fetch the number of stars
console.log("fetching data for " + name);
fetch("https://api.github.com/repos/" + repository)
.then((response) => response.json())
.then((data) => {
// check the console for the logs
console.log("fetched data for " + name);
setStars(data.stargazers_count);
});
});
return (
<p>
{name} has {likes} likes, {unlikes} unlikes and {stars} stars!{" "}
<button onClick={handleLikeClick}>Like</button>{" "}
<button onClick={handleUnlikeClick}>Unlike</button>
</p>
);
}
function LibraryList({ libraries }) {
return (
<div>
{libraries.map(({ name, repository }) => (
<CoolLibrary name={name} repository={repository} />
))}
</div>
);
}
ReactDOM.render(
<LibraryList
libraries={[
{ name: "React", repository: "facebook/react" },
{ name: "Vue.js", repository: "vuejs/vue" },
{ name: "Angular", repository: "angular/angular" },
{ name: "Svelte", repository: "sveltejs/svelte" },
{ name: "Ember.js", repository: "emberjs/ember.js" }
]}
/>,
document.getElementById("root")
);

By default, useEffect's callback is called after every render, so if any prop or state changes, the number of stars will be fetched again. Even though likes and unlikes are unrelated to the number of stars, a change in their values triggers useEffect. Check the console outputs on CodePen for more!

In order to prevent the extra fetch calls, we can pass an empty dependency array to useEffect.

function CoolLibrary({ name, repository }) {
// create local states
const [likes, setLikes] = React.useState(0);
const [unlikes, setUnlikes] = React.useState(0);
const [stars, setStars] = React.useState(0);
// handle button click for like
function handleLikeClick() {
setLikes(likes + 1);
}
// handle button click for unlike
function handleUnlikeClick() {
setUnlikes(unlikes + 1);
}
// `useEffect` hook with an empty dependency array
React.useEffect(() => {
// fetch the number of stars
console.log("fetching data for " + name);
fetch("https://api.github.com/repos/" + repository)
.then((response) => response.json())
.then((data) => {
// check the console for the logs
console.log("fetched data for " + name);
setStars(data.stargazers_count);
});
}, []);
return (
<p>
{name} has {likes} likes, {unlikes} unlikes and {stars} stars!{" "}
<button onClick={handleLikeClick}>Like</button>{" "}
<button onClick={handleUnlikeClick}>Unlike</button>
</p>
);
}
function LibraryList({ libraries }) {
return (
<div>
{libraries.map(({ name, repository }) => (
<CoolLibrary name={name} repository={repository} />
))}
</div>
);
}
ReactDOM.render(
<LibraryList
libraries={[
{ name: "React", repository: "facebook/react" },
{ name: "Vue.js", repository: "vuejs/vue" },
{ name: "Angular", repository: "angular/angular" },
{ name: "Svelte", repository: "sveltejs/svelte" },
{ name: "Ember.js", repository: "emberjs/ember.js" }
]}
/>,
document.getElementById("root")
);

As we can check from CodePen's console, clicking Like and Unlike buttons doesn't trigger useEffect with an empty dependency array. It runs only once after the first render.

As useEffect watches the changes in its dependencies, we can create a new state named points and compute its value through likes and unlikes.

function CoolLibrary({ name, repository }) {
// create local states
const [likes, setLikes] = React.useState(0);
const [unlikes, setUnlikes] = React.useState(0);
const [stars, setStars] = React.useState(0);
const [points, setPoints] = React.useState(0);
// handle button click for like
function handleLikeClick() {
setLikes(likes + 1);
}
// handle button click for unlike
function handleUnlikeClick() {
setUnlikes(unlikes + 1);
}
// `useEffect` hook with an empty dependency array
React.useEffect(() => {
// fetch the number of stars
console.log("fetching data for " + name);
fetch("https://api.github.com/repos/" + repository)
.then((response) => response.json())
.then((data) => {
// check the console for the logs
console.log("fetched data for " + name);
setStars(data.stargazers_count);
});
}, []);
// `useEffect` hook depending on `likes` and `unlikes` states
React.useEffect(() => {
setPoints(likes - unlikes);
}, [likes, unlikes]);
return (
<p>
{name} has {points} points and {stars} stars!{" "}
<button onClick={handleLikeClick}>Like</button>{" "}
<button onClick={handleUnlikeClick}>Unlike</button>
</p>
);
}
function LibraryList({ libraries }) {
return (
<div>
{libraries.map(({ name, repository }) => (
<CoolLibrary name={name} repository={repository} />
))}
</div>
);
}
ReactDOM.render(
<LibraryList
libraries={[
{ name: "React", repository: "facebook/react" },
{ name: "Vue.js", repository: "vuejs/vue" },
{ name: "Angular", repository: "angular/angular" },
{ name: "Svelte", repository: "sveltejs/svelte" },
{ name: "Ember.js", repository: "emberjs/ember.js" }
]}
/>,
document.getElementById("root")
);

Whenever likes or unlikes changes, setPoints will be called with the latest values.

useMemo

useMemo derives values from other variables with the following signature:

useMemo(<create function>, <dependencies>);

As you remember, we used useEffect to compute points through likes and unlikes in the useEffect section. useMemo could be used for the same purpose.

function CoolLibrary({ name, repository }) {
// create local states
const [likes, setLikes] = React.useState(0);
const [unlikes, setUnlikes] = React.useState(0);
const [stars, setStars] = React.useState(0);
// compute `points` through `likes` and `unlikes`
const points = React.useMemo(() => likes - unlikes, [likes, unlikes]);
// handle button click for like
function handleLikeClick() {
setLikes(likes + 1);
}
// handle button click for unlike
function handleUnlikeClick() {
setUnlikes(unlikes + 1);
}
// `useEffect` hook with an empty dependency array
React.useEffect(() => {
// fetch the number of stars
console.log("fetching data for " + name);
fetch("https://api.github.com/repos/" + repository)
.then((response) => response.json())
.then((data) => {
// check the console for the logs
console.log("fetched data for " + name);
setStars(data.stargazers_count);
});
}, []);
return (
<p>
{name} has {points} points and {stars} stars!{" "}
<button onClick={handleLikeClick}>Like</button>{" "}
<button onClick={handleUnlikeClick}>Unlike</button>
</p>
);
}
function LibraryList({ libraries }) {
return (
<div>
{libraries.map(({ name, repository }) => (
<CoolLibrary name={name} repository={repository} />
))}
</div>
);
}
ReactDOM.render(
<LibraryList
libraries={[
{ name: "React", repository: "facebook/react" },
{ name: "Vue.js", repository: "vuejs/vue" },
{ name: "Angular", repository: "angular/angular" },
{ name: "Svelte", repository: "sveltejs/svelte" },
{ name: "Ember.js", repository: "emberjs/ember.js" }
]}
/>,
document.getElementById("root")
);

Even if React re-renders the component, if the dependency values are the same, the create function won't be called again. Therefore, useMemo is especially effective for time-consuming computations.

Custom Hooks

React allows converting stateful logic units into custom hooks. Creating custom React hooks reduces code duplication and simplifies testing. Any JavaScript function can be a custom hook, but custom React hooks start with use prefix by convention.

In our CoolLibrary component, we have two similar buttons increasing the number of likes and unlikes. Button clicks are handled by handleLikeClick and handleUnlikeClick functions. We can create useCounter custom hook to count the button clicks. Here useCounter manages its own state and reused for both like and unlike buttons.

// custom hook for likes and unlikes
function useCounter() {
// create a local state
const [count, setCount] = React.useState(0);
function increase() {
setCount(count + 1);
}
// return `count` value and and `increase` function
return {
count,
increase
};
}
function CoolLibrary({ name, repository }) {
// call custom hook
const likeCounter = useCounter();
const unlikeCounter = useCounter();
// create a local state
const [stars, setStars] = React.useState(0);
// compute `points` through like and unlike counts
const points = React.useMemo(() => likeCounter.count - unlikeCounter.count, [
likeCounter,
unlikeCounter
]);
// `useEffect` hook with an empty dependency array
React.useEffect(() => {
// fetch the number of stars
console.log("fetching data for " + name);
fetch("https://api.github.com/repos/" + repository)
.then((response) => response.json())
.then((data) => {
// check the console for the logs
console.log("fetched data for " + name);
setStars(data.stargazers_count);
});
}, []);
return (
<p>
{name} has {points} points and {stars} stars!{" "}
<button onClick={likeCounter.increase}>Like</button>{" "}
<button onClick={unlikeCounter.increase}>Unlike</button>
</p>
);
}
function LibraryList({ libraries }) {
return (
<div>
{libraries.map(({ name, repository }) => (
<CoolLibrary name={name} repository={repository} />
))}
</div>
);
}
ReactDOM.render(
<LibraryList
libraries={[
{ name: "React", repository: "facebook/react" },
{ name: "Vue.js", repository: "vuejs/vue" },
{ name: "Angular", repository: "angular/angular" },
{ name: "Svelte", repository: "sveltejs/svelte" },
{ name: "Ember.js", repository: "emberjs/ember.js" }
]}
/>,
document.getElementById("root")
);

Summary

Even though useState, useEffect and useMemo are the most common hooks, React has other useful built-in hooks. We suggest skimming all hooks and spending more time when a specific hook is required.

We will publish more detailed guides on React, follow @crudfulcom and join Discord server for more!