Irbisa · Cheatsheet

Patterns & Architecture

Composition patterns, compound components, render props and HOCs, error boundaries, portals, folder structure, design systems, monorepos

Senior Developer · 20 questions

  1. 01

    Is the container/presentational component split still useful now that we have hooks?

    The idea still holds (keep rendering apart from data and side effects), but the split now usually sits between a custom hook and a component that renders, not between two component layers.

  2. 02

    How do the SOLID principles apply to React components and hooks?

    SOLID was written for classes, but each rule has a React form: one reason to change per component or hook, extension through composition instead of new flags, wrappers that keep the contract of what they wrap, narrow props, and dependencies passed in rather than imported.

    import type { ReactNode } from 'react'; // I: ask for the fields you use, not the whole `user`function Avatar({ name, src }: { name: string; src: string }) {  return <img src={src} alt={name} className="avatar" />;}…
  3. 03

    How do you do dependency injection in React, for example to swap the API client in tests or Storybook?

    React has no DI container: the injection points are props, context and the module system.

    import { createContext, use, type ReactNode } from 'react'; interface Api {  getUser(id: string): Promise<User>;  saveUser(user: User): Promise<void>;}…
  4. 04

    How do you avoid prop drilling without moving everything into global state?

    First restructure: pass ready-made elements through children or slot props, so intermediate components stop forwarding props they never use.

    import type { ReactNode } from 'react'; // before: Page -> Layout -> Sidebar -> UserMenu all forward `user`// after: Layout only places what it is givenfunction Layout({ sidebar, children }: { sidebar: ReactNode; children: ReactNode }) {  return (…
  5. 05

    What are compound components and how do you build one with context?

    Compound components are a set of components that only work together and share implicit state through a context the parent provides, like <Tabs>, <Tabs.Tab> and <Tabs.Panel>.

    import { createContext, use, useState, type ReactNode } from 'react'; const TabsContext = createContext<{ active: string; setActive: (id: string) => void } | null>(null); function useTabs() {  const ctx = use(TabsContext);…
  6. 06

    What is a render prop, and when would you still choose one over a custom hook?

    A render prop is a function prop that a component calls to find out what to render, passing its internal state as arguments, like renderItem={(item) => ...}.

    import { useState, type ReactNode } from 'react'; interface SelectProps<T> {  items: T[];  getKey: (item: T) => string;  renderOption: (item: T, state: { selected: boolean }) => ReactNode;…
  7. 07

    What is a higher-order component, what problems does it have, and what replaced it?

    A higher-order component (HOC) is a function that takes a component and returns a new one with extra props or behaviour, like withAuth(Page) or the old Redux connect.

    import type { ComponentType } from 'react';import { Navigate } from 'react-router'; function withAuth<P extends object>(Wrapped: ComponentType<P>) {  function WithAuth(props: P) {    const { user, isLoading } = useAuth();…
  8. 08

    What is a headless component or headless UI library (Radix, React Aria, TanStack Table), and why would you build on one?

    A headless component or library ships behaviour without markup or styles: state, keyboard handling, focus management and ARIA wiring, while you render and style every element.

    import { useId, useState } from 'react'; // Behaviour and ARIA only: no markup, no stylesfunction useDisclosure(initialOpen = false) {  const [open, setOpen] = useState(initialOpen);  const panelId = useId();…
  9. 09

    What is an error boundary, and which errors does it not catch?

    An error boundary catches errors thrown while rendering its subtree (and in lifecycle methods) and shows a fallback instead of unmounting the whole app.

    import { Component, type ErrorInfo, type ReactNode } from 'react'; interface Props { fallback: ReactNode; children: ReactNode }interface State { hasError: boolean } class ErrorBoundary extends Component<Props, State> {…
  10. 10

    Find the bug: when api.save() fails, the error boundary fallback never appears and the console shows an unhandled rejection.

    function SaveButton() {  async function handleClick() {    await api.save(); // rejects when the server is down  }  return <button onClick={handleClick}>Save</button>;} function Editor() {  return (    <ErrorBoundary fallback={<p>Something went wrong</p>}>      <SaveButton />    </ErrorBoundary>  );}

    Error boundaries catch errors thrown during rendering, and an event handler (let alone a promise that rejects later) runs outside rendering, so the rejection goes to unhandledrejection and the UI carries on as if nothing happened.

  11. 11

    What is a portal, and when do you need one?

    createPortal(children, domNode) renders children into a different DOM node (usually at the end of document.body) while they keep their place in the React tree.

    import type { ReactNode } from 'react';import { createPortal } from 'react-dom'; interface ModalProps {  open: boolean;  onClose: () => void;…
  12. 12

    What does this log when the user clicks the button rendered through a portal?

    import { createPortal } from 'react-dom'; function App() {  return (    <div onClick={() => console.log('App div')}>      <Popup />    </div>  );} function Popup() {  return createPortal(    <button onClick={() => console.log('button')}>Open</button>,    document.body,  );}

    It logs button, then App div: React propagates its events along the React tree, and there the portal content is a child of the div, even though in the DOM the button sits directly in document.body.

  13. 13

    How would you structure folders in a large React app?

    Group code by feature, not by file type: features/checkout holds its components, hooks, API calls and tests together, while shared/ holds generic UI and utilities that know nothing about any feature.

    src/  app/              providers, router, global styles  pages/            route components: compose features, no logic  features/    checkout/      api.ts…
  14. 14

    Review this component as you would in a code review: what problems do you see, and in what order would you raise them?

    function TeamMembers({ teamId, onSelect }: { teamId: string; onSelect: (user: User) => void }) {  const [users, setUsers] = useState<User[]>([]);  const [query, setQuery] = useState('');  const [visible, setVisible] = useState<User[]>([]);   useEffect(() => {    fetch(`/api/teams/${teamId}/users`).then((res) => res.json()).then(setUsers);  }, []);  useEffect(() => setVisible(users.filter((u) => u.name.includes(query))), [users, query]);  return (    <div>      <input value={query} onChange={(e) => setQuery(e.target.value)} />      {visible.map((u) => <div key={u.id} onClick={() => onSelect(u)}>{u.name}</div>)}    </div>  );}

    Raise the problems in order of impact: first the bugs (the effect ignores teamId, has no cleanup and trusts any response), then the derived state kept in an effect, then accessibility, and only then naming and style.

  15. 15

    How do you design a reusable component that works both controlled and uncontrolled?

    Follow the native input contract: accept value with onChange for controlled use and defaultValue for uncontrolled use, keep internal state only while value is undefined, and call onChange in both modes.

    import { useState, type ReactNode } from 'react'; function useControllableState<T>(value: T | undefined, defaultValue: T, onChange?: (next: T) => void) {  const [inner, setInner] = useState(defaultValue);  const isControlled = value !== undefined;  const current = isControlled ? value : inner;…
  16. 16

    Where do you place error boundaries in an app, and how do they work with Suspense and retry?

    One at the root as the last resort, one per route so a broken page keeps the navigation alive, and one around each widget that can fail on its own (a chart, a comments block).

    import { Suspense, type ReactNode } from 'react';import { ErrorBoundary } from 'react-error-boundary';import { useLocation } from 'react-router'; function RouteShell({ children }: { children: ReactNode }) {  const { pathname } = useLocation();…
  17. 17

    How do you set up error monitoring for a production React app so that the reports are readable and actionable?

    Capture every source of errors, make each report point at your code and your release, filter the noise, and alert on what is new or growing instead of on every event.

    import * as Sentry from '@sentry/react';import { createRoot } from 'react-dom/client'; Sentry.init({  dsn: import.meta.env.VITE_SENTRY_DSN,  release: import.meta.env.VITE_GIT_SHA, // ties every event to one deploy…
  18. 18

    How do you stop features from importing each other's internals, and what are the pitfalls of barrel files?

    Give each feature one public entry (features/cart/index.ts) and enforce it with a lint rule that rejects deep imports like features/cart/ui/CartRow.

    // eslint.config.jsexport default [  {    files: ['src/**/*.{ts,tsx}'],    rules: {      'no-restricted-imports': ['error', {…
  19. 19

    When does a monorepo make sense for React projects, and how do you set one up?

    A monorepo pays off when several apps share code (a design system, an API client, configs) and you want one PR to change a library and all its consumers together.

    {  "name": "@acme/ui",  "version": "0.0.0",  "private": true,  "type": "module",  "exports": {…
  20. 20

    How would you upgrade a large app from React 18 to React 19, and what tends to break?

    Go through React 18.3 first, which warns about every API React 19 removes, fix those warnings, run the official codemods, then upgrade the libraries whose peer range stops at 18.

    # 1. React 18.3 is 18.2 plus a warning for every API React 19 removesnpm install react@18.3 react-dom@18.3# run the app and the tests, fix every warning # 2. Codemods for the removed APIs and for the type changesnpx codemod@latest react/19/migration-recipe…