React Hooks: A Comprehensive Guide with Examples

React Hooks make it easy to use state and other React features in functional components. They simplify managing component logic and state, offering a cleaner and more readable alternative to class-based components.

In this tutorial, we'll cover the basics of React Hooks and show you how to use them in your applications!

Prerequisites

Make sure you have the following:

  • A basic knowledge and understanding of how React works.
  • NodeJS and npm (Node Package Manager) are installed on your computer. If not, you can read more on creating a NodeJS with Express web server to get setup!

Introduction to React Hooks

React Hooks are functions that allow you to "hook into" React state and lifecycle features from functional components. They were introduced in React 16.8 to address the following issues:

  • Reusing stateful logic between components. Before, you had to use higher-order components or render props to do this.
  • Complex component logic in class components made the code harder to read and maintain.

React provides a set of built-in hooks that allows you to create custom ones that we'll go over now!

Basic Hooks

useState

The useState hook allows you to add state to functional components. It returns an array with two elements: the current state value and a function to update it.

import React, { useState } from "react";

function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>Count: {count}</p>
<button onclick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

export default Counter;

useEffect

The useEffect hook enables you to perform side effects in your components, such as data fetching, DOM manipulation, and subscribing to a data source. It takes two arguments: a function to run the side effect and an array of dependencies that trigger the side effect when any of them change.

import React, {useState, useEffect } from "react";

function Fetcher() {
const [data, setData] = useState([]);

useEffect(() => {
fetch("https://api.domain.com/data")
.then((response) => response.json())
.then((data) => setData(data));
}, []);

return (
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}

export default Fetcher;

useContext

The useContext hook allows you to access the context values provided by a Context.Provider higher up in the component tree.

import React, { useContext } from "react";

const ThemeContext = React.createContext("light");

function ThemedComponent() {
const theme = useContext(ThemeContext);

return <div>Current Theme: {theme}</div>;
}

export default ThemedComponent;

Additional Hooks

useRef

The useRef hook allows you to create mutable object references that persist across renders. This can be useful for accessing and modifying DOM elements directly.

import React, { useRef, useEffect } from "react";

function FocusInput() {
const inputRef = useRef(null);

useEffect(() => {
inputRef.current.focus();
}, []);

return <input ref={inputRef} />;
}

export default FocusInput;

useReducer

The useReducer hook is an alternative to useState for managing complex state logic. It takes a reducer function and an initial state.

import React, { useReducer } from "react";

function counterReducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default;
return state;
}
}

function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });

return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
</div>
);
}

export default Counter;

useCallback and useMemo

useCallback and useMemo are used for optimizing performance by memoizing values. useCallback memoizes functions, and useMemo memoizes values.

import React, { useState, useMemo, useCallback } from "react";

function Calculation() {
const [count, setCount] = useState(0);

const calculation = useMemo(() => {
return count * 2;
}, [count]);

const clickHandler = useCallback(() => {
setCount(count + 1);
}, [count]);

return (
<div>
<p>Result of calculation: {calculation}</p>
<button onClick={clickHandler}>Increment Count</button>
</div>
);
}

export default Calculation;

Custom Hooks

You can create your own custom hooks to encapsulate and reuse component logic across different components.

import { useState, useEffect } from "react";

function Fetcher(url) {
const [data, setData] = useState([]);

useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((data) => setData(data));
}, [url]);

return data;
}

export default Fetcher;

Rules of Using React Hooks

When working with React Hooks, you should follow these rules:

  • Only call hooks at the top level of your functional component or custom hook.
  • Only call hooks from within functional components or custom hooks, not regular JavaScript functions.
  • Ensure the order of the hook calls is consistent across renders.

Common Hook Patterns

Some common patterns you might encounter when using hooks include managing form state, handling authentication, and using context for global state management. Each of these patterns can be achieved by creating custom hooks tailored to your specific needs.

Conclusion

React Hooks provide a more elegant and functional way to manage state and side effects in your applications. By using basic hooks like useState and useEffect, along with custom hooks, you can create cleaner, more maintainable code for your React components.

Written by: Josh Rowe

Last Updated: January 06, 2025
Created: September 27, 2023