Skip to content

React · Hooks

React useContext Hook — Complete Guide for Interviews

7 min read Updated 2026-06-23 Share:

Practice useContext interview questions

Why useContext comes up in every React interview

Context is React's built-in solution to one of the oldest frontend problems: making data available to components deep in the tree without threading it through every layer. If you've ever seen a codebase where a user prop is passed from AppLayoutSidebarNavAvatar when only Avatar actually uses it, you've seen the problem context solves. Interviewers use context questions to probe your understanding of React's data flow model, the performance model underneath the surface, and when to reach for a library instead.

The three-step pattern: create, provide, consume

Context always involves three pieces. First you create a context object with a default value. Then a Provider component in the tree passes the real value down. Finally, any descendant calls useContext to read it.

// 1. create once, usually in its own file
export const ThemeContext = createContext('light')

// 2. provide high in the tree
function App() {
  const [theme, setTheme] = useState('dark')
  return (
    <ThemeContext.Provider value={theme}>
      <Page />
    </ThemeContext.Provider>
  )
}

// 3. consume anywhere below
function Button() {
  const theme = useContext(ThemeContext)
  return <button className={theme}>click</button>
}

useContext replaces the older <ThemeContext.Consumer> render-prop approach with a simple function call. It's always cleaner, and it composes correctly when a component needs to read from multiple contexts.

What the default value actually does

The argument to createContext is the default value — but it's used only when there is no matching Provider anywhere above the consuming component. It does not act as a fallback when a Provider exists but passes undefined or null.

const Ctx = createContext('default')

// no provider -> consumer sees 'default'
function Isolated() {
  return <Child />
}

// provider explicitly passing undefined -> consumer sees undefined
function App() {
  return <Ctx.Provider value={undefined}><Child /></Ctx.Provider>
}

The default value is mainly useful for tests and storybook where you want to render a consumer without wrapping it in the full Provider tree. Set it to a realistic value that makes the component render sensibly in isolation.

The performance model: what triggers a re-render

Every component that calls useContext(Ctx) re-renders whenever the value prop on the nearest <Ctx.Provider> changes, compared by Object.is. This is the most important performance fact about context and the source of most context bugs in production.

The classic trap: putting an object literal directly in the Provider.

// new object on every render -> every consumer re-renders
<UserContext.Provider value={{ user, setUser }}>

Every render of the Provider creates a fresh {}, so Object.is sees a new value and all consumers re-render — even if user and setUser haven't changed at all. Fix it with useMemo:

const value = useMemo(() => ({ user, setUser }), [user])
<UserContext.Provider value={value}>

Now consumers only re-render when user actually changes.

React.memo does not help

A common misconception: wrapping a consumer in React.memo will protect it from unnecessary context re-renders. It won't. React.memo skips re-renders when props don't change, but it has no effect on context — a memoized component still re-renders when a context value it reads changes.

const Avatar = React.memo(function Avatar() {
  const { user } = useContext(UserContext) // still re-renders on UserContext change
  return <img src={user.avatarUrl} />
})

The correct place to apply the optimization is the Provider (memoize the value), not the consumers.

Splitting context by change frequency

When one context carries both stable data and frequently-changing data, all consumers re-render on any change — even if they only care about the stable part. The fix is to split the context.

// monolithic: all consumers re-render on every notification change
<AppContext.Provider value={{ user, notifCount }}>

// split: UserContext consumers are unaffected by notification updates
<UserContext.Provider value={user}>
  <NotifContext.Provider value={notifCount}>
    <App />
  </NotifContext.Provider>
</UserContext.Provider>

A useful heuristic: separate contexts by who cares about the change, not by what makes semantic sense together.

The Provider + custom hook pattern

Rather than exporting the raw context object and letting consumers import it directly, encapsulate everything in a custom hook. The hook enforces correct usage and gives a helpful error when a component is accidentally rendered outside its Provider.

// AuthContext.tsx
const AuthContext = createContext(null)

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  const value = useMemo(() => ({ user, setUser }), [user])
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}

export function useAuth() {
  const ctx = useContext(AuthContext)
  if (!ctx) throw new Error('useAuth must be inside AuthProvider')
  return ctx
}

Consumers call useAuth() instead of useContext(AuthContext). If they're outside the Provider, they get a clear error rather than a silent null. This pattern also makes it easy to swap the underlying implementation later without changing call sites.

How to update context from a consumer

Include the setter alongside the data in the context value. Consumers call the setter directly — it lives in the Provider's state, so calling it updates the state, causes the Provider to re-render, and propagates the new value to all consumers.

const ThemeContext = createContext(null)

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('dark')
  const value = useMemo(() => ({ theme, setTheme }), [theme])
  return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
}

function Toggle() {
  const { theme, setTheme } = useContext(ThemeContext)
  return <button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>{theme}</button>
}

setTheme has a stable identity (it's a useState setter), so including it in the memoized value doesn't cause extra renders.

Context vs. state management libraries

Context is not a replacement for Redux, Zustand, or Jotai — it's a different tool:

ContextLibrary (Redux/Zustand)
Re-render granularityAll consumers of a valueOnly subscribers of the slice
SelectorsNoYes
DevToolsNoneYes
MiddlewareNoYes (thunk, saga)
Good forAuth, theme, localeHigh-churn global state

Use context for data that changes rarely and is needed broadly — user session, theme, language. Use a library when many components subscribe to different slices of rapidly changing state, or when you need time-travel debugging and middleware.

Testing context consumers

Wrap the component under test in the Provider with a controlled test value. The standard pattern is a helper render function.

function renderWithAuth(ui, { user = { name: 'Test' } } = {}) {
  return render(<AuthProvider initialUser={user}>{ui}</AuthProvider>)
}

test('shows username in header', () => {
  renderWithAuth(<Header />)
  expect(screen.getByText('Test')).toBeInTheDocument()
})

Alternatively, mock the hook for unit-style tests — but a real Provider with controlled values is more robust and closer to production.

Common interview questions at a glance

  • What triggers a context consumer to re-render? Any change to the Provider's value prop, compared by Object.is. New object references trigger it even if contents are unchanged.
  • Does React.memo protect against context re-renders? No — memoize the value at the Provider, not the consumer.
  • When would you split a context? When different consumers care about different parts that change at different frequencies.
  • What is the default value used for? When no Provider exists above the component; not when a Provider passes undefined.
  • Context vs Redux? Context for low-churn global data; Redux/Zustand for high-churn data with selective subscriptions and middleware.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel