Home
React
Advanced Hook: useContext()
Daniel Nguyen
Daniel Nguyen
July 13, 2025
1 min

Sometimes, multiple components need access to the same state. Lifting state up works well for nearby components, but prop drilling becomes cumbersome when components are deeply nested or far apart in the component tree.

React Context lets components share a value without passing props through every intermediate component.

1. The Problem: Custom Hooks Don’t Share State

A custom Hook reuses logic, not state.

function useCount() {
const [count, setCount] = useState(0)
return {
count,
increment: () => setCount(c => c + 1),
}
}

Using this Hook in two components creates two independent state instances:

function DisplayCount() {
const { count } = useCount()
return <div>{count}</div>
}
function IncrementCount() {
const { increment } = useCount()
return <button onClick={increment}>Increment</button>
}

Clicking the button updates only the state owned by IncrementCount.

To share state, both components need to read from the same state owner.

2. Share State with Context

Create a Context and a Provider that owns the state.

import {
createContext,
useContext,
useState,
type ReactNode,
} from 'react'
type CountContextValue = {
count: number
increment: () => void
}
const CountContext = createContext<CountContextValue | null>(null)
function CountProvider({ children }: { children: ReactNode }) {
const [count, setCount] = useState(0)
function increment() {
setCount(c => c + 1)
}
return (
<CountContext.Provider value={{ count, increment }}>
{children}
</CountContext.Provider>
)
}
function useCount() {
const context = useContext(CountContext)
if (context === null) {
throw new Error('useCount must be used inside CountProvider')
}
return context
}

Consume the shared state from either component:

function DisplayCount() {
const { count } = useCount()
return <div>Count: {count}</div>
}
function IncrementCount() {
const { increment } = useCount()
return <button onClick={increment}>Increment</button>
}

Wrap both components in the same Provider:

function App() {
return (
<CountProvider>
<DisplayCount />
<IncrementCount />
</CountProvider>
)
}

Now both components access the same state. Clicking the button updates the value displayed by DisplayCount.

3. How Context Works

  • Provider: Makes a value available to descendants.
  • Consumer: Reads the nearest matching Context value using useContext.
  • Updates: When the Provider’s value changes, consumers using that Context receive the updated value.
  • Nested Providers: The closest matching Provider takes precedence.
  • Custom Hook: Encapsulates Context access and provides a clear error when used outside the Provider.

4. Context and Re-renders

Consider this Provider:

<CountContext.Provider value={{ count, increment }}>
{children}
</CountContext.Provider>

The object { count, increment } is recreated whenever the Provider renders. Because its identity changes, Context consumers may re-render even when count has not changed.

For larger applications, you can stabilize the value with useMemo and callbacks with useCallback when profiling shows that it helps.

Another option is to split state and actions into separate Contexts, so components that only dispatch updates do not subscribe to the changing state Context.

Important: Context is a value-sharing mechanism, not a complete state-management or performance-optimization solution.


Tags

#ReactAdvancedHooks

Share

Daniel Nguyen

Daniel Nguyen

Frontend Developer

Frontend developer specializing in React, Next.js, and JavaScript. Writing practical guides on modern web development at Dev98.

Expertise

React
Next.js
JavaScript
TypeScript
Python

Social Media

githublinkedinyoutubewebsite

Related Posts

Advanced Hook
Advanced Hook: useSyncExternalStore()
July 19, 2025
1 min
Dev98

Dev98

React · Next.js · Web development