Skip to solution
mediumFrontend

What are React's design principles?

1.2k views
01

Understand the problem

Question presented to candidate: "Beyond the API, what principles has the React team said guide its design — and where can you see them in the library you use every day?"

What a strong answer should cover:

  • Composition — the headline principle. Components by different authors must work together, and you must be able to add functionality without rippling changes.
  • Common abstraction — React resists adding features that can be built in userland; a feature has to earn its place in the core.
  • Escape hatches — React is pragmatic. useRef, useEffect, flushSync, portals, and dangerouslySetInnerHTML all exist to let you step outside the declarative model.
  • Stability — gradual migration paths, deprecation warnings before removal, and codemods rather than hard breaks.
  • Interoperability — it must wrap non-React code and be wrappable by it.
  • Scheduling — React controls when work happens, which is precisely what made concurrent rendering possible later.
  • Optimised for tooling — explicit, statically analysable APIs so linters, compilers, and devtools can reason about your code.
  • Developer experience and debugging — component stacks, warnings, and DevTools are treated as first-class.
  • The strongest version connects each principle to something concrete: Hooks rules exist for tooling; key exists for scheduling; children exists for composition.

Clarifying questions expected:

  • "Do you want the documented principles, or my read on the trade-offs they imply?"

Code / implementation expected: No. This is a discussion question; concrete examples of each principle matter more than code.

design principlesdeclarativecomponentsarchitecture
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for senior React interviews — assumes broad familiarity with the API. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. This is a discussion question rather than a mechanical one,

Solution ready — 2 min read

Classified // press E to declassify

04

Explore the playground snippets

Escape hatches in practice: ref, effect, portal, and flushSync
Run Playground
import { useState, useRef, useEffect, useLayoutEffect } from "react";
import { createPortal, flushSync } from "react-dom";

export default function App() {
  const [log, setLog] = useState([]);
  const [showPortal, setShowPortal] = useState(false);
  const listRef = useRef(null);

  // ESCAPE HATCH 1 — useRef: a value that survives renders WITHOUT causing one.
  // Rendering cannot see this, which is exactly the point.
  const renderCount = useRef(0);
  renderCount.current++;

  // ESCAPE HATCH 2 — useEffect: synchronising with something outside React.
  useEffect(() => {
    document.title = `${log.length} entries`;
    return () => { document.title = "React"; };
  }, [log.length]);

  // ESCAPE HATCH 3 — useLayoutEffect: read layout before the browser paints,
  // so the scroll adjustment never flickers.
  useLayoutEffect(() => {
    if (listRef.current) listRef.current.scrollTop = listRef.current.scrollHeight;
  }, [log]);

  const add = (text) => setLog((l) => [...l, text]);

  // ESCAPE HATCH 4 — flushSync: opt out of batching and force the DOM to
  // update synchronously, so we can measure it in the same tick.
  const addAndMeasure = () => {
    flushSync(() => add("measured immediately"));
    // Without flushSync the DOM would not be updated yet at this line.
    add(`height right after flush: ${listRef.current.scrollHeight}px`);
  };

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6 }}>
      <p>
        Renders: <strong>{renderCount.current}</strong>{" "}
        <span style={{ color: "#666", fontSize: 13 }}>(tracked in a ref, so reading it causes no render)</span>
      </p>

      <div
        ref={listRef}
        style={{ height: 110, overflow: "auto", border: "1px solid #ddd", borderRadius: 6, padding: 8, marginBottom: 10 }}
      >
        {log.length === 0 && <em style={{ color: "#888" }}>empty</em>}
        {log.map((l, i) => <div key={i} style={{ fontSize: 13 }}>{l}</div>)}
      </div>

      <button onClick={() => add("plain entry " + (log.length + 1))}>Add</button>{" "}
      <button onClick={addAndMeasure}>Add with flushSync</button>{" "}
      <button onClick={() => setShowPortal((s) => !s)}>
        {showPortal ? "Close" : "Open"} portal
      </button>

      {/* ESCAPE HATCH 5 — createPortal: render outside the parent DOM position. */}
      {showPortal &&
        createPortal(
          <div style={{
            position: "fixed", right: 16, bottom: 16, background: "#1f2937",
            color: "white", padding: "10px 14px", borderRadius: 8, fontSize: 13,
          }}>
            Rendered into document.body, not into the div above.
          </div>,
          document.body,
        )}

      <p style={{ color: "#666", fontSize: 13 }}>
        Each button demonstrates a documented escape hatch. The list auto-scrolls
        via useLayoutEffect so you never see it jump.
      </p>
    </div>
  );
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 27 of 119 decoded in the React.js track. One more won't hurt.

Back to track