🧠 How to Share State Across React Components Using Context and Custom Hooks
When working with React, managing state between components is a common challenge—especially when those components are siblings or deeply nested. While lifting state up is often the go-to solution, there are cases where it’s just not practical. In this post, you’ll learn how to share state between components without prop drilling, using React Context and custom hooks.
Let’s start with a simple custom hook:
function useCount() {const [count, setCount] = useState(0)const increment = () => setCount((c) => c + 1)return { count, increment }}
Now imagine you use this hook in two separate components:
function DisplayCount() {const { count } = useCount()return <div>{count}</div>}function IncrementCount() {const { increment } = useCount()return <button onClick={increment}>Increment</button>}function App() {return (<div><DisplayCount /><IncrementCount /></div>)}
Looks good, right? But there’s a bug: Clicking the increment button doesn’t update the displayed count. Why?
useCount()
is a Separate State InstanceEvery time you call useCount()
, a new piece of state is created. So DisplayCount
and IncrementCount
are not sharing state at all—they’re isolated.
To share state across components without lifting it manually through props, we can use React Context.
Let’s walk through how to do this properly.
import { createContext, ReactNode, useContext, useState } from 'react'type CountContextType = {count: numberincrement: () => void}const CountContext = createContext<CountContextType | null>(null)
We initialize the context with null
to force consumers to only use it within a proper provider.
function CountProvider({ children }: { children: ReactNode }) {const [count, setCount] = useState(0)const increment = () => setCount((c) => c + 1)const value = { count, increment }return <CountContext.Provider value={value}>{children}</CountContext.Provider>}
This component manages the shared state and passes it through the context to its children.
function useCount() {const context = useContext(CountContext)if (!context) {throw new Error('useCount must be used within a CountProvider')}return context}
Using this hook ensures that consuming components have access to the shared state, and we get a useful error if the context is missing.
function DisplayCount() {const { count } = useCount()return <div>{count}</div>}function IncrementCount() {const { increment } = useCount()return <button onClick={increment}>Increment</button>}function App() {return (<CountProvider><div><DisplayCount /><IncrementCount /></div></CountProvider>)}
🎉 Now the IncrementCount
button updates the same count
that DisplayCount
is showing!
You might wonder: “Can’t I just provide a default value to createContext
and skip the Provider?”
Technically yes—but there’s a catch: You lose reactivity. If the context holds a plain object instead of state, updating it won’t cause consumers to rerender. That’s why it’s best practice to default the context to null
and force usage within a proper provider.
Let’s say you’re using React Router’s useSearchParams()
to access query params in multiple components.
You might try calling useSearchParams()
in each component separately, like:
const [searchParams, setSearchParams] = useSearchParams()
But then you realize: ⚠️ this creates multiple subscriptions to the URL change event, and the state is no longer synchronized across components.
To solve this, wrap your app in a provider that calls useSearchParams()
once, and expose the value via context. Then, use a custom useSearchParams()
hook that accesses that context instead of calling the original hook directly.
The example above uses
useContext
. Some experimental React versions introduce a newuse()
hook, but for apps using React ≤ 18.2, stick withuseContext
.
React Context is a powerful tool when you need to implicitly share state across components. Whether you’re building compound components, managing shared URL state, or dealing with components that live in different parts of your tree—Context + custom hooks = a scalable, clean solution.
Quick Links
Legal Stuff
Social Media