Skip to the solution
HardFrontend6 min read

What changed with Context in React 19 (<Context> as a provider)?

63 views

The problem

Question presented to candidate: "React 19 changed the Context syntax. What changed, and does existing code still work?"

What a strong answer should cover:

  • You can now render the context object itself as the provider: <ThemeContext value={x}> instead of <ThemeContext.Provider value={x}>.
  • Context.Provider still works β€” this is additive, not a breaking change, and existing code needs no migration.
  • The implementation detail worth knowing: Context.Provider === Context is now true, which is why both syntaxes render identically.
  • Context.Consumer is deprecated in favour of useContext, though it still exists. The render-prop consumer predates hooks.
  • Nothing about the behaviour changed: consumers still all re-render on a value change, the default value still applies only when there is no provider, and there is still no selector mechanism.
  • Related React 19 context change: use(Context) can read a context conditionally, because use allocates no hook slot β€” unlike useContext, which obeys the Rules of Hooks.
  • Migration: cosmetic. A codemod exists; there is no urgency.

Clarifying questions expected:

  • "Are we on React 19 across the whole codebase, or is this a shared library that must support 18?"

Code / implementation expected: Yes β€” both syntaxes side by side, plus the conditional use(Context) read.

  • react
  • react-19
  • context

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 8 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactBoth provider syntaxes, and a conditional read with use()
import { createContext, useContext, use, useState } from "react";

const ThemeContext = createContext("default (no provider)");

// The React 19 implementation detail that makes both spellings work.
const providerIsContext = ThemeContext.Provider === ThemeContext;

function ReaderWithUseContext() {
  // useContext obeys the Rules of Hooks: unconditional, top level.
  const theme = useContext(ThemeContext);
  return <Line label="useContext" value={theme} />;
}

function ReaderWithUse({ skip }) {
  // use() may be called CONDITIONALLY β€” it allocates no hook slot, so there is
  // no call-order mapping to corrupt. useContext here would be illegal.
  if (skip) return <Line label="use() β€” skipped" value="did not read" />;
  const theme = use(ThemeContext);
  return <Line label="use() β€” read" value={theme} />;
}

function Line({ label, value }) {
  return (
    <p style={{ margin: "4px 0", fontSize: 14 }}>
      <code style={{ display: "inline-block", minWidth: 150 }}>{label}</code>
      {value}
    </p>
  );
}

function Panel({ title, children }) {
  return (
    <section style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, marginBottom: 10 }}>
      <h4 style={{ margin: "0 0 6px" }}>{title}</h4>
      {children}
    </section>
  );
}

export default function App() {
  const [skip, setSkip] = useState(false);

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6, maxWidth: 470 }}>
      <p style={{ background: "#eef6ff", padding: 10, borderRadius: 6, fontSize: 14 }}>
        <code>ThemeContext.Provider === ThemeContext</code> is{" "}
        <strong>{String(providerIsContext)}</strong> β€” which is why both spellings
        below render identically.
      </p>

      {/* React 19: the context itself is the provider */}
      <ThemeContext value="dark (new syntax)">
        <Panel title="New: <ThemeContext value=...>">
          <ReaderWithUseContext />
        </Panel>
      </ThemeContext>

      {/* Still fully supported, no warning */}
      <ThemeContext.Provider value="light (old syntax)">
        <Panel title="Old: <ThemeContext.Provider value=...>">
          <ReaderWithUseContext />
        </Panel>
      </ThemeContext.Provider>

      {/* No provider at all β€” the default applies */}
      <Panel title="No provider β€” the createContext default">
        <ReaderWithUseContext />
      </Panel>

      <ThemeContext value="read conditionally">
        <Panel title="use() inside a condition">
          <ReaderWithUse skip={skip} />
          <button onClick={() => setSkip((s) => !s)}>
            {skip ? "read the context" : "skip the read"}
          </button>
        </Panel>
      </ThemeContext>

      <p style={{ color: "#666", fontSize: 13 }}>
        The last panel returns before calling use() when skipping β€” legal,
        because use() stores nothing on the fiber. Doing that with useContext
        would throw a Rules of Hooks error.
      </p>
    </div>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 119 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
End of the topic

That was the last React.js question.

Pick another topic