Skip to the solution
MediumFrontend7 min read

What are React's design principles?

1.2k views

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 principles
  • declarative
  • components
  • architecture

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

7 min read Β· 10 sections

Try it in the playground

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

ReactEscape hatches in practice: ref, effect, portal, and flushSync
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>
  );
}

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 27 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
Up nextMedium

What is the difference between `useState` and `useReducer`?

Continue