Specialization

The classic argument for inheritance is specialization: X is a kind of Y, so X extends Y and overrides a few members. In React 7,897 you get the same result by rendering the general component from the specific one and pre-filling its props. WelcomeDialog is a kind of Dialog, so it renders a Dialog.

A specific component built from a general oneJavaScript
const Dialog = ({ title, message, children }) => (
  <FancyBorder>
    <h3 className="Dialog-title">{title}</h3>
    <p className="Dialog-message">{message}</p>
    {children}
  </FancyBorder>
);
const WelcomeDialog = () =>                     // no props: every hole is decided here
  <Dialog title="Welcome" message="Thank you for visiting our spacecraft." />;
function SignUpDialog({ onSignUp }) {           // specialized, and still open at the bottom
  const [name, setName] = useState('');
  return (
    <Dialog title="Mars Program" message="How should we refer to you?">
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button onClick={() => onSignUp(name)}>Sign Me Up!</button>
    </Dialog>);
}

Read the two specializations side by side and the advantage over subclassing is concrete. WelcomeDialog fixes props and nothing else. SignUpDialog fixes props and fills the children hole and owns state — three kinds of extension, none of which required Dialog to expose a protected member or to know that either specialization exists.

The pattern nests. A design system exposes one Button with variant and size props; the application defines PrimaryButton and DangerButton as one-line specializations, so a change of house style is a change in one file. A specialization can itself be specialized: DeleteConfirmDialog renders ConfirmDialog, which renders Dialog.