React Hooks opens a new world in the React ecosystem. However, it can be quite confusing for beginners working with React for the first time.
Therefore, in this tutorial, I’ll help you understand what React Hooks are, the various kinds of hooks available in React, and when and how to use them. Additionally, you will learn how to create custom hooks in React.
React Hooks, put simply, are basic JavaScript functions that manage various tasks in React. That’s right, they’re not as complex as you might think.
So, why are people so excited about them? Let’s find out.
In traditional React development, managing component state and handling side effects was primarily done within class components. (Don’t worry about the terms “state” and “side-effect” ; we’ll learn about them later in this post). However, this approach had some limitations and made code harder to read and maintain.
For example, just look at the code below and see how overwhelming it was to manage the state before the introduction of hooks. We had to manage states by binding them to the component using the JavaScript keyword.
So, finally, in the React v16.8 release, hooks were introduced to address these issues and provided a more elegant solution for functional components.
In ReactJS, there are hooks such as useState, useEffect, useReducer, useMemo, useRef, and many others. In this post, we will try to understand the two most commonly used hooks: useState and useEffect.
Fun Fact: Do you know you can create your own React hooks? Well, I also guide you through creating your custom hooks shortly.
The useState hook in React allows the functional component to manage and manipulate state data. So, you might be wondering what the “state data” here is.
Okay, let me tell you, the state data here refers to the data within a component that changes over time through events or external effects, like HTTP calls.
For example, clicking on pause in a video player should pause the current playing video, or selecting a colour from the colour picker should select the current colour.
To do this, the components need to remember things here: the current state of the video and the current color value.
Now, let’s use the useState hook using the example of the colour picker above and build a short application step by step.
export default function ColorPicker() {
return (
<main>
<h1>React Color Picker</h1>
</main>
)
}
import { useState } from "react";
// const state = useState(); ❎ Cannot be use outside ColorPicker
export default function ColorPicker() {
const colorState = useState() //✅ Correct way of calling useState hook
console.log('Color State : ', colorState)
return (
<main>
<h1>React Color Picker</h1>
</main>
)
}
Color State : ▼ Array(2)
0 : undefined
1 : ƒ ()
import { useState } from "react";
export default function ColorPicker() {
// const colorState = useState()
const [color, setColor] = useState()
return (
<main>
<h1>React Color Picker</h1>
</main>
)
}
import { useState } from "react";
export default function ColorPicker() {
// const colorState = useState()
const [color, setColor] = useState('#8C00FF')
return (
<main>
<h1>React Color Picker</h1>
</main>
)
}
Let’s take the next step. Add a <div> and style it to display the selected color, and include an <input> tag of type “color” for selecting the colour from the input. Set the value property of the input to the color (initialState), and attach an onChange function called onColorChange() to update the initial state.
When the input value changes, the onColorChange() function is triggered. Inside this function, we call the setColor function and pass e.target.value as an argument to it.
This will cause the setColor function to update the color value, which in turn updates and reflects the color change wherever it’s used.
import { useState } from "react";
export default function ColorPicker() {
const [color, setColor] = useState('#8C00FF')
function onColorChange(e){
setColor(e.target.value)
}
const divStyle = {
width : '200px',
height : '200px',
backgroundColor : color // color variable (#8c00ff)
}
return (
<main>
<h1>React Color Picker</h1>
<div style={divStyle}>
<h2>Color : {color}</h2>
</div>
<input type='color' value={color} onChange={onColorChange} />
</main>
)
}
useEffect Hook allows us to execute code after the component has been rendered. It is useful for tasks like data fetching in case you are not using any server data management library like React Query, etc. You can also use it for setting up subscriptions or handling side effects like using any external libraries, e.g., a video plugin.
The syntax for useEffect hook is very simple. It takes a callback function and a dependency array, and the dependency array controls when the effect should run.
useEffect(callbackFunction, dependencyArray);
Now, the dependency array can either be empty, or we can pass values such as props, state, and contexts from the component scope. A useEffect hook with an empty dependency array will run only for the first time when the component renders, and if the useEffect hook has a dependency array, it will run every time the value in the dependency array changes.
Note: We can also neglect the dependency array, however it can cause major performance issues as the effect will run after every render of the component, including the initial one, without relying on any specific values.
Now, let’s see how we can use this hook in our application.
import { useEffect } from "react";
export default function Todo() {
useEffect(() => {}, []);
//useEffect(callBackFunction, [dependenciesArray]);
return (
<div>
<h1>Todo App</h1>
</div>
)
}
import { useEffect } from "react";
export default function Todo() {
//callBackFunction to fetch todods
function fetchTodos(){
fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(json => console.log(json))
}
//useEffect(callBackFunction, [dependenciesArray]);
useEffect(fetchTodos, []);
return (
<div>
<h1>Todo App</h1>
</div>
)
}
.
import { useEffect } from "react";
export default function Todo() {
const [todos, setTodos] = useState([])
//callBackFunction to fetch todods
function fetchTodos(){
fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(json => setTodos(json))
}
//useEffect(callBackFunction, [dependenciesArray]);
useEffect(fetchTodos, []);
return (
<div>
<h1>Todo App</h1>
</div>
)
}
import { useEffect } from "react";
export default function Todo() {
const [todos, setTodos] = useState([])
//callBackFunction to fetch todods
function fetchTodos(){
fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(json => setTodos(json))
}
//useEffect(callBackFunction, [dependenciesArray]);
useEffect(fetchTodos, []);
return (
<div>
<h1>Todo App</h1>
{!todos.length && <h1>loading...</h1>}
<ul>
{todos?.map(({id, title}) => (
<li key={id}>{title}</li>
))}
</ul>
</div>
)
}
By now, you might be familiar that whenever the state changes, the component re-renders which ensures that the User Interface(UI) stays up to date. However, re-rendering components can be resource-intensive, and it might affect the overall performance of your application.
For example, suppose in a component of your app, there is a button that opens a modal, and also, there is a heavy calculation going on. When you open or close the modal, you may not want to run the heavy calculation.
So, to solve this problem, you can cache or memoize the data that require resource-intensive operations, and in React, you can do that using useMemo and useCallback hooks. Let’s understand both of them one by one.
useMemo Hook caches the calculated value of an operation whenever the state changes, or re-render occurs.
The syntax to use useMemo hook is:
const cachedValue = useMemo(calculateValue, dependencies)
The useCallback hook is another function that enables us to cache data within React applications. It accepts a function and its array of dependencies. Like the useMemo hook, the function runs every time the dependencies change.
const cachedFunction = useCallback(function, [dependencies])
Let’s look at the example below:
Within the SearchProduct component, the useCallback hook keeps track of the searched item and ensures that the right item is retrieved from the array.
The useReducer hook is another great hook for managing states within React applications. However, unlike the useState hook, it is commonly used in components with numerous states across multiple event handlers.
The useReducer hook has four main components: the state, the reducer function, the action, and the dispatch function.
The reducer function manipulates the state directly and returns a copy of the result, and the dispatch function triggers the reducer function when various events occur.
The action is an object containing a type and a payload property. The type property specifies the exact action to be executed by the reducer function, and the payload can accept data from the user or other parts of the application.
Let’s consider an example using a contact form that accepts a name, email address, and message from the users.
import { useReducer } from "react";
const ACTIONS = {
UPDATE_NAME: "updateName",
UPDATE_EMAIL: "updateEmail",
UPDATE_MESSAGE: "updateMessage",
};
const reducer = (state, action) => {
switch (action.type) {
case ACTIONS.UPDATE_NAME:
return { ...state, name: action.payload.value };
case ACTIONS.UPDATE_EMAIL:
return { ...state, email: action.payload.value };
case ACTIONS.UPDATE_MESSAGE:
return { ...state, message: action.payload.value };
default:
return state;
}
};
export default function App() {
const [state, dispatch] = useReducer(reducer, {
name: "",
email: "",
message: "",
});
const handleSubmit = (e) => {
e.preventDefault();
console.log({ state });
};
return (
<div>
<h2>Contact Us</h2>
<form onSubmit={handleSubmit}>
<label>Full Name</label>
<input
type='text'
value={state.name}
onChange={(e) => {
dispatch({
type: ACTIONS.UPDATE_NAME,
payload: {
value: e.target.value,
},
});
}}
/>
<label>Email Address</label>
<input
type='email'
value={state.email}
onChange={(e) => {
dispatch({
type: ACTIONS.UPDATE_EMAIL,
payload: {
value: e.target.value,
},
});
}}
/>
<label>Message</label>
<textarea
rows={6}
value={state.message}
onChange={(e) => {
dispatch({
type: ACTIONS.UPDATE_MESSAGE,
payload: {
value: e.target.value,
},
});
}}
/>
<button type='submit'>SEND</button>
</form>
</div>
);
}
The code snippet above accepts the email, username, and message from the user and stores them in a state using the useReducer hook.
Let me explain better:
const [state, dispatch] = useReducer(reducer, {
name: "",
email: "",
message: "",
});
When declaring the useReducer hook, it accepts two parameters – the reducer function and the state object.
const ACTIONS = {
UPDATE_NAME: "updateName",
UPDATE_EMAIL: "updateEmail",
UPDATE_MESSAGE: "updateMessage",
}
const reducer = (state, action) => {
switch (action.type) {
case ACTIONS.UPDATE_NAME:
return { ...state, name: action.payload.value };
case ACTIONS.UPDATE_EMAIL:
return { ...state, email: action.payload.value };
case ACTIONS.UPDATE_MESSAGE:
return { ...state, message: action.payload.value };
default:
return state;
}
};
The reducer function is in charge of updating the state values within a useReducer hook. It accepts the state and action as parameters.
The action parameter contains a payload object holding the current value of the input field. It also has a type property that enables us to update the right state when there are numerous states to update.
Finally, each input field triggers the reducer function via the dispatch function. The dispatch function accepts an object containing the action type and payload required by the reducer function.
<input type='text'
value={state.name}
onChange={(e) => {
dispatch({
type: ACTIONS.UPDATE_NAME,
payload: {
value: e.target.value,
},
});
}}
/>
So far, we’ve been using the built-in hooks provided by React. However, there are scenarios where you might require custom hooks tailored for specific purposes.
For example, you might need a hook to determine if a user is online, to check if your app is running in the background, or for other specialized tasks.
But why and when should you create a custom hook? Imagine you have an application where one component is performing a CPU-intensive task, and you want to reduce its impact when the user switches to a different application tab. Meanwhile, another component is handling animations, and you want to stop the animation when the tab is behind some other application.
One option is to write code in each of these components to monitor their visibility, but a more organized and efficient approach is to create a custom hook to handle this common task. In this way, we can abstract common functionalities, making our code cleaner and easier to manage, and that’s the main purpose of creating a custom hook.
Let’s build the custom hook usePageVisibility to check if the app is visible or not.
Remember to use use before the name of any custom hook you build.
For simplicity, we have written the usePageVisibility in the same component; you can write it in a different folder and use it wherever you need.
Let’s understand the usePageVisibility hook :
import './App.css'
import { useState, useEffect } from "react";
// Custom hook to check if the tab is in the background
function usePageVisibility() {
const [isVisible, setIsVisible] = useState(true);
function handleVisibilityChange() {
setIsVisible(document.visibilityState === 'visible');
};
useEffect(() => {
document.addEventListener('visibilitychange', handleVisibilityChange);
return function(){
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
return isVisible;
}
export default function App() {
const isTabVisible = usePageVisibility();
return (
<div>
<h1>Check Page Visibility</h1>
<p>Is the tab visible? { isTabVisible ? 'Yes' : 'No' }</p>
</div>
);
}
It uses the useState hook to create a state variable isVisible, which represents whether the page is currently visible (it is true by default).
The handleVisibilityChange function is responsible for updating the isVisible state based on changes in the page’s visibility state (visible or hidden).
The useEffect hook is used to:
The hook returns the isVisible state, providing a boolean value to indicate whether the web page is currently visible to the user or not.
We are calling the usePageVisibility hook in the `App` and it is assigned to the isTabVisible, the value of isTabVisible will be either true or false. Based on this variable we are printing “Yes” or “ No” on the UI.
Using React hooks effectively can greatly improve the development of React applications. However, there are common mistakes and pitfalls that you make and can avoid to prevent unexpected issues. Here are some common mistakes to watch out for when using React hooks:
export default function App(){
// Call your hooks here
}
useEffect(() => {
// Missing dependencies can lead to bugs
}, []);
useEffect(() => {
// Causes an infinite render loop without dependency management
setState(state + 1);
}, []);
state.property = 'new value'; // Incorrect, don't modify state directly
const MyComponent = () => {
// Common logic duplicated in multiple components
useEffect(() => {
// Common logic
});
};
React hooks have changed the way we manage state and side effects in functional components. These are just plain JavaScript functions that simplify and enhance the development of React applications.
Here’s a quick recap of the key points we’ve covered in this post:
React hooks are one of the essential concepts in React, and understanding it will help you to build applications in an efficient way.
In this post, we explored two of the most commonly used hooks, useState and useEffect.
The useReducer hook is used for managing states within applications with complex state management and transitions.
You’ve also learned how to create custom hooks for specific tasks, such as checking if the web app tab is in the background.
Custom hooks help us to reuse logic across different components.
We have also learned how to avoid some common mistakes that many beginners might face, like: not following the rules of hooks, neglecting dependencies in the dependency array, causing infinite render loops, incorrect usage of state updater functions, and more.
I hope this post is helpful and you have learned about how to use hooks in your React app.
If you want to read more about ReactJS then start reading some of my recent articles.
Curiosity didn't just get the better of the cat—it led you here! If this piece sparked your interest, imagine what else is waiting.
Wander into the wonder; your next tech treasure awaits in my HackerNoon collection.
Also published here.