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.
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.
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.
| Concept | Shown in Example |
|---|---|
useEffect | Runs once on component mount |
| Side effect | API call to fetch a joke |
State (useState) | Stores and displays the fetched joke |
| Conditional rendering | Shows 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 Array | When the Effect Runs |
|---|---|
| Omitted | Runs after every render (not recommended unless intential) |
[] (empty array) | Runs only once on component mount |
[someVar] | Runs whenever someVar changes |
Dependency array example
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;
| Part | Meaning |
|---|---|
useEffect | Hook to perform side effects (like DOM updates, network requests, etc.) |
| Function | Runs after every render where count changes |
[count] | Dependency array — only rerun the effect when count changes |
document.title | Updates 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.
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> ); }
Read A Simple Explanation of React.useEffect() for more explanation on useEffect.
Live clock useEffect example
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> ); }
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:
-
Creating the context: You start by creating a Context object using
React.createContext()(or createContext() if imported directly). -
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. -
Consuming the context with
useContext: Finally, in any component that needs to access this context, you use theuseContextHook. You pass the Context object you created earlier touseContext, 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.
-
useContextis 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:
'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;
}
'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>
);
}
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:
Then when you toggle the button:
The ThemeProvider uses Tailwind classes to achieve the light and dark on theme.

