Articles

How to Use Hooks in ReactJS with Examples

Master React hooks with our comprehensive guide covering `useState`, `useEffect`, `useContext` and more - with practical examples to transform your ReactJS development workflow.

React.js is a powerful library for building dynamic user interfaces with reusable components. Initially, it offered two main types of components: functional and class-based. Though functional components provided a neat and straightforward syntax, they were less capable than class components, particularly when dealing with state or lifecycle events.

To fill this gap, React 16.8 introduced Hooks, a new feature that added powerful functionality such as state management and side effects to functional components.

What are React hooks?

React Hooks are a powerful feature introduced in React 16.8 that allow developers to use state, side effects, and other React features inside functional components. In most cases, they eliminate the need for class components, making it easier to write clean, reusable, and maintainable code. With hooks, functional components can handle complex logic that was once only possible with classes.

Here are some benefits of React hooks:

  • Simplified codebase: Hooks reduce boilerplate by removing the need for class syntax, this bindings, and constructor functions.

  • Better logic reuse: Custom hooks let you extract and reuse stateful logic across components without duplication.

  • Improved readability: Functional components with hooks are generally easier to read and follow, especially in smaller teams.

  • Co-located logic: Hooks keep related logic together. For example, fetching data and updating state can live side by side.

  • Easier testing: Hook-based logic is more modular and testable, especially when broken into smaller custom hooks.

React offers various built-in hooks to help manage component state, side effects, and context more effectively. Let’s explore the most used ones and see how they simplify React development.

Related Course

Learn React: Hooks

Leverage hooks, a powerful feature of function components, to use states without creating classes.Try it for free

Commonly used ReactJS hooks

React includes a variety of hooks designed to handle different aspects of component logic. Some focus on managing state, others deal with side effects, while a few provide ways to access context or references. Let us understand some essential hooks you’ll encounter and use frequently in React projects.

How to use the useState hook in React

Unlike class components, functional components don’t have built-in state management. React’s useState hook fills this gap by allowing you to easily add and manage local state inside functional components.

The useState hook initializes the state in a functional component. It returns an array with two elements: the current state value and a function to update that state.

Syntax of the useState hook is:

const [state, setState] = useState(initialValue); 

Parameters:

  • initialValue: The initial value of the state, which can be any data type (number, string, object, array, etc.).

  • state: The current state value at any render.

  • setState: The function used to update the state.

Example:

Here’s a basic counter example that shows how to initialize state and update it on a button click:

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>
);
}

In this code:

  • useState(0) initializes the state variable count with a value of 0.

  • count holds the current state value, which React keeps track of across re-renders.

  • setCount is a function provided by useState to update the count value. When setCount is called, React updates the state and re-renders the component with the new value.

  • The button has an onClick event that calls setCount(count + 1), which increments the counter each time it’s clicked.

  • React re-renders the <p> element with the updated count on every click.

A basic counter interface with a “Count: 0” label and an “Increment” button. Each click on the button increases the count by 1 and updates the number on screen

When to use the useState hook?

Use the useState hook whenever your component needs to keep track of data that can change over time and affect what’s rendered. It’s ideal for local state that belongs only to that component.

Common scenarios include:

  • Form fields: Storing and updating user input values

  • UI toggles: Showing or hiding elements like modals, dropdowns, or tabs

  • Counters or timers: Tracking clicks, elapsed time, or other incrementing values

  • Simple user interactions: Managing likes, selections, or toggled settings

If your state is local to a component and doesn’t need to be shared globally or across multiple components, useState is a clean and effective choice.

But what if your component needs to run code when it renders, fetch data, or responds to changes in state or props? That’s where the useEffect hook can be used.

Handling side effects in React using useEffect hook

Functional components don’t have lifecycle methods like componentDidMount or componentDidUpdate. That’s where useEffect comes in, to run the code when a component renders, updates, or unmounts.

The useEffect hook allows you to handle side effects in your component like fetching data, updating the DOM, or setting up event listeners. It effectively replaces class-based lifecycle methods, bringing that logic into functional components.

How does the useEffect hook work?

  • useEffect accepts a function that runs after the component renders.

  • This function can return another function, which React will call when the component unmounts—great for cleanup.

  • A second argument, the dependency array, controls when the effect runs.

Example:

Here is a simple example that fetches user data from an API when the component mounts using the useEffect hook:

import { useEffect, useState } from "react";
function UserComponent() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch("https://api.example.com/user/1")
.then((res) => res.json())
.then((data) => setUser(data));
// Cleanup function (optional)
return () => {
console.log("Component unmounted");
};
}, []); // Runs only once on mount
return <div>{user ? user.name : "Loading..."}</div>;
}

In this code:

  • useEffect(() => {...}, []): Runs once when the component mounts, just like componentDidMount.

  • The empty array [] ensures it doesn’t run on updates.

  • The returned function logs when the component unmounts, which is helpful for cleanup like removing event listeners or aborting fetches.

When to use the useEffect hook

Use the useEffect hook whenever your component needs to perform side effects—operations that go beyond rendering UI. These include fetching data, subscribing to services, manually updating the DOM, or setting timers.

Typical use cases include:

  • Fetching or updating data when the component mounts

  • Setting up and cleaning up event listeners or subscriptions

  • Triggering animations or external library functions

  • Syncing local state with external systems (e.g., localStorage, APIs)

If your logic previously lived in lifecycle methods like componentDidMount, componentDidUpdate, or componentWillUnmount, useEffect is your go-to hook in functional components.

Now, let’s say you need to hold on to a value without triggering a re-render. That’s where the useRef hook comes in.

What is the useRef hook in React

The useRef hook is useful when you need a way to persist values across renders without triggering re-renders. It returns a mutable object with a .current property that you can update freely. React won’t re-render when it changes. useRef is commonly used for two primary purposes:

  • Accessing and manipulating DOM elements directly

  • Holding on to mutable values (like timers or previous values) without causing re-renders

Syntax of the useRef hook is:

import { useRef } from 'react'; 
function MyComponent() { 
  const myRef = useRef(null); 
  return <input ref={myRef} />; 
} 

Here:

  • useRef(null) initializes the ref with null.

  • The ref is attached to an element using the ref attribute.

  • myRef.current will point to the DOM element after render.

Example:

Let’s see a common use case where clicking a button focuses an input field:

import { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Focus the input</button>
</div>
);
}

In this code:

  • We create a ref called inputRef and attach it to the <input> element.

  • On button click, inputRef.current.focus() is called to focus the input.

  • No re-renders happen when the ref changes.

A text input field with a “Focus the input” button beside it. When the button is clicked, the cursor automatically appears inside the input field, indicating focus.

When to use the useRef hook

Use the useRef hook when you need to:

  • Access or interact with DOM elements directly, such as focusing an input or measuring size

  • Store mutable values (like timers, interval IDs, or counters) that should persist between renders without triggering re-renders

  • Keep track of previous values (like tracking the last state or prop)

In short, reach for useRef when you need a persistent reference that doesn’t affect your component’s rendering logic.

As your application grows, sharing data between many components can get tricky. That’s where useContext helps by making it easier to pass data through the component tree.

Managing global state with useContext hook

Passing props through multiple layers of components can become messy and hard to manage. The useContext Hook solves this problem by allowing components to access shared data without manually passing it down each level.

Unlike useState, which handles local component state, useContext is ideal for values needed in many places, like themes, user authentication, or app settings. Let us check how to use the useContext hook:

Step 1: Create a context using createContext()

To begin, you create a context using React.createContext():

const ThemeContext = React.createContext();

This creates a context object to share values across your component tree.

Step 2: Providing context

Use the context’s Provider component to make data available to any child component:

<ThemeContext.Provider value="dark">
<App />
</ThemeContext.Provider>

Now, any component inside <App /> can access the value "dark" using useContext.

Step 3: Consuming context with useContext

Inside any component that needs the shared value, use the useContext Hook:

const theme = useContext(ThemeContext);

This will return "dark" as provided earlier.

Example: Theme switcher

Let’s look at a basic example of switching themes using useContext:

import React, { createContext, useContext, useState } from "react";
// Create context
const ThemeContext = createContext();
// Context provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () =>
setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Consuming component
function ThemeButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme}>
Current Theme: {theme}
</button>
);
}
// App component
function App() {
return (
<ThemeProvider>
<ThemeButton />
</ThemeProvider>
);
}

In this code:

  • ThemeContext holds the shared theme data.

  • ThemeProvider wraps the app and manages the theme state.

  • ThemeButton uses useContext to access and toggle the theme.

  • The entire tree inside ThemeProvider has access to the same theme value.

When to use the useContext hook

Use the useContext hook when you need to share data across many components without passing props manually through each level. It’s especially helpful for app-wide settings like themes, user authentication, language preferences, or any state that multiple components need to access.

Avoid using useContext for frequently changing or deeply nested local state—it’s better suited for global or semi-global data that remains relatively stable.

Now that we’ve seen how to share global state, let’s look at useReducer, a hook ideal for managing more complex state logic.

Simplifying complex state logic with useReducer hook

Handling state transitions with multiple conditions or nested values can quickly become tricky. Instead of juggling several useState calls, useReducer offers a cleaner, more predictable way to manage state, especially when the logic mirrors a state machine or when actions need to be clearly defined.

Syntax of the useReducer hook is:

const [state, dispatch] = useReducer(reducerFunction, initialState); 

Parameters:

  • reducerFunction: A function that takes the current state and an action, and returns a new state.

  • initialState: The starting value for the state, typically an object or a number.

Return value:

Returns an array: [state, dispatch], where state holds the current value and dispatch triggers state updates.

Example: Managing a counter with multiple actions

import React, { useReducer } from "react";
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return initialState;
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
);
}

Here:

  • We define an initial state with a count of 0.

  • The reducer function handles different action.type values to update the state.

  • dispatch() sends actions to the reducer.

  • The UI buttons call dispatch() with respective action types to trigger changes.

A counter interface with three buttons: “+”, “-”, and “Reset”. Clicking “+” increases the count, “-” decreases it, and “Reset” sets it back to 0, updating the number shown each time.

When to use the useReducer hook

Use useReducer when:

  • You have complex state logic that involves multiple sub-values or conditions

  • Updating state with useState becomes hard to track or maintain

  • You need a more centralized and testable approach to handling state transitions

While built-in hooks handle many cases, there are custom hooks that let you extract and reuse complex logic across components without duplication. Let us explore them.

Creating custom hooks in React

Custom hooks let you encapsulate and reuse stateful logic across multiple components, making your code more modular and maintainable. They’re just regular JavaScript functions that use one or more hooks internally and follow the naming convention of starting with use.

Custom hooks help avoid repetition and keep your components cleaner by moving logic into separate reusable functions.

How to write your own hook in React

A custom hook is a function that:

  • Starts with the use prefix (e.g., useLocalStorage)

  • Can call other hooks like useState, useEffect, or useRef

  • Accepts parameters to make it reusable

  • Returns values or functions for components to use

Example: A useLocalStorage hook

Let’s create a simple custom hook to store and retrieve values from localStorage:

import { useState, useEffect } from "react";
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(storedValue));
}, [key, storedValue]);
return [storedValue, setStoredValue];
}

Here:

  • useState initializes the value either from localStorage (if available) or from a default.

  • useEffect updates localStorage whenever the stored value changes.

  • The hook returns the value and a setter, just like useState.

Now you can use it like this in your component:

const [theme, setTheme] = useLocalStorage("theme", "light");

This hook abstracts the localStorage logic, making the component cleaner and more focused on UI.

When to use custom hooks

Use custom hooks when you find yourself repeating the same logic in multiple components, such as handling forms, timers, or window dimensions. They’re perfect for isolating logic and improving readability without relying on class components or third-party libraries.

Best practices for using React hooks

To write clean, efficient, and maintainable React code, keep these best practices in mind when using hooks:

  • Call Hooks at the Top Level: Always use hooks at the top of your components or custom hooks—not inside loops, conditions, or nested functions.

  • Use Descriptive Custom Hook Names: Start with use and clearly describe what the hook does, like useAuth or useLocalStorage, to improve readability and reuse.

  • Manage useEffect Dependencies Carefully: Include all necessary variables in the dependency array to avoid stale data or infinite loops.

  • Clean Up Side Effects: When using useEffect, return a cleanup function to prevent memory leaks—especially with event listeners or timers.

  • Never Mutate State Directly: Always use the setter function returned by useState or useReducer to update state without modifying the original value.

When not to use hooks

  • When a feature can be achieved using plain JavaScript or a one-off function, don’t force a hook.

  • In simple components with no state or side effects.

  • For logic that should be kept global (e.g., Redux or context logic) instead of component-specific.

Hooks are powerful but not always the right tool for every scenario. Use them thoughtfully to keep your components clean, focused, and efficient.

Conclusion

In this article, we explored the core concepts of React hooks, including useState, useEffect, useContext, useRef, useReducer, and custom hooks. These hooks allow functional components to manage state, handle side effects, interact with the DOM, and organize logic more effectively, making your React code cleaner and more efficient.

To continue building your React skills and get hands-on practice with hooks, check out Codecademy’s Learn React course. It’s a great way to apply your knowledge and advance your front-end development journey.

Frequently asked questions

1. When should I use useEffect vs. useLayoutEffect?

  • Use useEffect for side effects that don’t need to block the browser paint, like fetching data or setting up subscriptions. It runs after the component is rendered and the DOM is painted.

  • Use useLayoutEffect when you need to read the layout from the DOM and synchronously re-render before the browser paints. It’s useful for measuring elements or synchronizing animations but use it sparingly as it can affect performance.

2. Why does useEffect keep running in a loop?

This usually happens when you forget to include a dependency array or incorrectly place changing variables inside it. When no dependency array is provided, useEffect runs after every render. The effect will keep triggering if the dependencies constantly change (e.g., a state or prop). Make sure to review the dependency list carefully to avoid infinite loops.

3. Can I conditionally call Hooks in my components?

No. Hooks must be called unconditionally and in the same order on every render. Conditional calls can break the Rules of Hooks and lead to unpredictable behavior. Instead, use conditions inside the hook’s body, not around the hook call itself.

4. What are the rules of Hooks, and what happens if I break them?

The two main rules of Hooks are:

  • Only call Hooks at the top level (not inside loops, conditions, or nested functions).
  • Only call Hooks from React function components or custom hooks.

Breaking these rules can result in bugs and inconsistent state behavior. React provides a linting plugin to help you catch violations.

5. Can I use custom hooks in class components?

No. Hooks—including custom hooks, only work with function components.

Codecademy Team

'The Codecademy Team, composed of experienced educators and tech experts, is dedicated to making tech skills accessible to all. We empower learners worldwide with expert-reviewed content that develops and enhances the technical skills needed to advance and succeed in their careers.'

Meet the full team