Skip to main content

Hooks

Hooks are a key concept of React. Hooks are special functions that are used inside React client-side components and add special features and behaviors to the component where they are used.

useState() is an important and commonly used Hook that enables you to manage data inside a component, which, when updated, tells React to update the UI accordingly.

React provides various built-in Hooks and they are not all focused on state management.

info

React hooks, such as useState, useEffect, and useContext are designed to handle user interactions, manage local state, and perform client-side data fetching. Use client components when you need to use React hooks or handle user interactions.

useEffect()

Sometimes we need components to interact with 'side effects', or effects outside of React. For example, to fetch data when a component is mounted. When a user launches the React application, we want to fetch the data to render in the application. How do we trigger this to happen?
With useEffect() we can initiate fetch() when the component mounts. The UI is rendered as the content is updated.

What is a side effect?

In React, a side effect is anything that:

  • Affects something outside the scope of the component, or

  • Interacts with the external world (browser APIs, network, storage, etc.).

fetch on component mount example

Here's a React example using useEffect to fetch a random joke from an API when the component mounts. In this case, we fetch on component mount rather than load the component without a joke and require a user click or similar to fatch the joke.

JokeFetcher.tsx
import { useState, useEffect } from 'react';

function JokeFetcher() {
const [joke, setJoke] = useState(null);

useEffect(() => {
fetch('https://official-joke-api.appspot.com/jokes/random')
.then((res) => res.json())
.then((data) => {
setJoke(data);
});
}, []);

if (!joke) {
return <p>Loading a joke...</p>;
}

return (
<div>
<h2>Here's a joke:</h2>
<p><strong>{joke.setup}</strong></p>
<p>{joke.punchline}</p>
</div>
);
}
export default JokeFetcher;

... and used in another component:

<JokeFetcher />

fetch() is an asynchronous request that returns a promise that resolves to a Response object - the HTTP response from the server. We convert the Response object to JSON and send the date (the joke) to the updater, setJoke.

ConceptShown in Example
useEffectRuns once on component mount
Side effectAPI call to fetch a joke
State (useState)Stores and displays the fetched joke
Conditional renderingShows a loading message while fetching

Optional dependency array

useEffect(() => { ...}, [ ]) takes a dependency array a the second argument. It controls how often and when the useEffect runs. Here are three common use cases for the dependency array:

Dependency ArrayWhen the Effect Runs
OmittedRuns after every render (not recommended unless intential)
[] (empty array)Runs only once on component mount
[someVar]Runs whenever someVar changes

Dependency array example

CounterWithTitle.tsx
import { useState, useEffect } from 'react';

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

useEffect(() => {
document.title = `Clicked ${count} times`;
}, [count]);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Click Me
</button>
</div>
);
}
export default CounterWithTitle;
PartMeaning
useEffectHook to perform side effects (like DOM updates, network requests, etc.)
FunctionRuns after every render where count changes
[count]Dependency array — only rerun the effect when count changes
document.titleUpdates the browser tab title with the current count

In this example, the side effect is updating the browser tab title with document.title. That’s outside the scope of the component UI. Changing document.title affects the global browser environment, not just the component.

With [count] as a dependency, the effect runs only when count changes. Check it in the live code editor below.

Live Editor


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

  useEffect(() => {
    document.title = `Clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click Me
      </button>
    </div>
  );
}


Result
Loading...
more

Read A Simple Explanation of React.useEffect() for more explanation on useEffect.

Live clock useEffect example

Live Editor
function Clock(props) {
  const [date, setDate] = useState(new Date());
  useEffect(() => {
    const timerID = setInterval(() => tick(), 1000);

    return function cleanup() {
      clearInterval(timerID);
    };
  });

  function tick() {
    setDate(new Date());
  }

  return (
    <div>
      <h2>It is {date.toLocaleTimeString()}.</h2>
    </div>
  );
}
Result
Loading...

useContext()

What is React context?

The React context API allows you to create a context that can share data down the component tree without manually passing props at every level (known as "prop drilling"). The setup of the context API involves:

  1. Creating the context: You start by creating a Context object using React.createContext() (or createContext() if imported directly).

  2. Providing the context with <Context.Provider>: You then wrap the part of your component tree that needs access to the context with a <Context.Provider>. This component takes a value prop, which is the data you want to make available to all children components nested within it.

  3. Consuming the context with useContext: Finally, in any component that needs to access this context, you use the useContext Hook. You pass the Context object you created earlier to useContext, and it will return the current value of the context provided by the nearest ancestor <Context.Provider>

Why is this useful?

  • Use of React context solves the Prop Drilling problem - where you have to pass props down through multiple layers of components, even if intermediate components don't directly use those props.

  • Data flows directly from the Provider to any consuming component, skipping intermediate components and making the code cleaner.

  • useContext is particularly useful for managing application-wide themes, user authentication status, or language preferences. 

Toggle theme context example

Here's an example for creating the theme context:

theme-provider.tsx
'use client';

import { createContext, useContext, useState } from 'react';

type Theme = 'light' | 'dark';

type ThemeContextType = {
theme: Theme;
toggleTheme: () => void;
};

const ThemeContext = createContext(undefined);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState('light');

const toggleTheme = () =>
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));

return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div className={theme === 'dark' ? 'bg-gray-900 text-white min-h-screen' : 'bg-white text-black min-h-screen'}>
{children}
</div>
</ThemeContext.Provider>
);
}

export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}

home-content.tsx
'use client';

import { useTheme } from './theme-provider';

export default function HomeContent() {
const { theme, toggleTheme } = useTheme();

return (
<div className="p-8 flex flex-col items-center justify-center space-y-4">
<h1 className="text-2xl font-bold">Current Theme: {theme}</h1>
<button
onClick={toggleTheme}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition"
>
Toggle Theme
</button>
</div>
);
}

page.tsx
import { ThemeProvider } from './theme-provider';
import HomeContent from './home-content';

export default function HomePage() {
return (
<ThemeProvider>
<HomeContent />
</ThemeProvider>
);
}

This example renders the following on mount:

React useContext Theme Light

Then when you toggle the button:

React useContext Theme Dark

The ThemeProvider uses Tailwind classes to achieve the light and dark on theme.