technology · react

React Cheatsheets

KDP Book Manifest & Metadata
Click to Expand & CopyManifest

Use the copy buttons below to copy metadata verbatim into the Amazon KDP Publishing forms.

Book Title
Subtitle
Target Audience
BISAC Subject Code
Keywords (Comma Separated)
Book Description (HTML)
KDP Categories
  • Books > Computers & Technology > Programming > React
  • Books > Computers & Technology > Web Development

React Cheatsheets

One-Page Quick References from Core Syntax to Advanced Patterns

usefulcheatsheets.com

React Cheatsheets

First Edition: 2026

Copyright © 2026 by usefulcheatsheets.com. All rights reserved.

No part of this book may be reproduced in any form or by any electronic or mechanical means, including information storage and retrieval systems, without written permission from the publisher, except for the use of brief quotations in a book review.

Publisher: usefulcheatsheets.comISBN: Not ApplicableBISAC Subject Code: COM051000

Welcome to React

React is a key topic in Technology development.

This reference book compiles comprehensive cheatsheets covering everything from fundamentals to advanced patterns.

Use this book as a daily reference or read it linearly to build your knowledge.

How to Use This Book

Each page is a visual cheatsheet with core concepts, practical steps, code snippets, and warnings.

usefulcheatsheets.com | Introduction
Useful Cheatsheetsusefulcheatsheets.com
React Conditional Rendering
Chapter 01 · Page 7
Beginner

React Conditional Rendering

Show or hide elements in React using if statements, ternaries, logical operators, and switch patterns.

TL;DR

  1. 01Use if statements to return different JSX based on conditions.
  2. 02Use ternary expressions inside JSX for inline conditionals.
  3. 03Use logical AND for simple show-or-hide patterns.

Tips

  1. 01Prefer if statements and switch for clarity, since they separate logic from JSX and are easier to test and refactor.

Warnings

  1. 01Avoid using <code>&&</code> with falsy values like <code>0</code> or empty strings, since they will render nothing instead of the string itself.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Conditional Rendering
Chapter 01 · Page 8
Beginner

React Conditional Rendering

(continued)

If Statements

  • Return different JSX based on a condition before the return statement.
    function UserInfo({ user }) {
      if (!user) return <p>Loading...</p>;
      return <div>{user.name}</div>;
    }
  • This is the clearest pattern when conditions are complex.
  • Use early returns to handle edge cases at the top of the component.
  • Combine multiple if statements for sequential checks.
  • Avoid nesting too many ifs — extract sub-components instead.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Conditional Rendering
Chapter 01 · Page 9
Beginner

React Conditional Rendering

(continued)

Ternary Operator

  • Use the ternary operator inside JSX for inline conditionals.
    <div>{user ? user.name : "Guest"}</div>
  • Read as "if user is truthy, show user.name, else show Guest".
  • Keep ternaries simple and readable — use if statements for complex logic.
  • Nest ternaries only for quick reads, not for multiple conditions.
    <div>{user ? (user.isAdmin ? "Admin" : "User") : "Guest"}</div>
  • Extract nested ternaries into separate components for clarity.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Conditional Rendering
Chapter 01 · Page 10
Beginner

React Conditional Rendering

(continued)

Logical AND Operator

  • Use && to render JSX only when a condition is true.
    <div>{user && <p>Hello, {user.name}!</p>}</div>
  • This hides the element when the condition is false without rendering anything.
  • Only use when there is no "else" case needed — otherwise use ternary.
  • Beware of falsy values — avoid using && with 0 or empty strings.
    {count > 0 && <p>{count} items</p>}
  • Use ternary instead if you need to show something when the condition is false.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Conditional Rendering
Chapter 01 · Page 11
Beginner

React Conditional Rendering

(continued)

Switch Patterns

  • Use a switch statement before the return for multiple conditions.
    function Status({ status }) {
      switch (status) {
        case "loading":
          return <p>Loading...</p>;
        case "success":
          return <p>Done!</p>;
        case "error":
          return <p>Error occurred</p>;
        default:
          return null;
      }
    }
  • Switch works well when you have many distinct states to handle.
  • Always include a default case for unexpected values.
  • Extract each case into a separate component for reusability.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Conditional Rendering
Chapter 01 · Page 12
Beginner

React Conditional Rendering

(continued)

Conditional Components

  • Create helper components to encapsulate conditional rendering logic.
    function IfAdmin({ children, user }) {
      return user?.isAdmin ? children : null;
    }
    <IfAdmin user={user}>
      <AdminPanel />
    </IfAdmin>
  • Use render props or children to control what is displayed.
  • Name components clearly to show their conditional purpose.
  • This pattern keeps components focused and testable.
  • Combine with custom hooks for more complex conditional logic.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Conditional Rendering
Chapter 01 · Page 13
Beginner

React Conditional Rendering

(FAQ)

FAQ

Use a ternary inside JSX when you need to choose between two inline values or elements. Reach for an if statement when the logic is complex or you're returning entirely different component trees, since it keeps your JSX cleaner and the logic easier to test.

The && operator short-circuits to the left operand when it's falsy, and React renders the number 0. Convert the left side to a boolean first: use !!count && or count > 0 && to avoid this.

Use a switch statement or extract the logic into a helper function that returns JSX. This keeps your render method readable and makes each branch independently testable, especially when you have more than two possible states.

No, if statements are not expressions and can't appear inside JSX curly braces. Move the conditional logic above the return statement or wrap it in an immediately invoked function expression, though the former is almost always cleaner.

Use a ternary expression in the className prop: className={isActive ? 'active' : 'inactive'}. For multiple conditional classes, the classnames or clsx library keeps the logic readable without manual string concatenation.

Useful Cheatsheetsusefulcheatsheets.com
React Event Handling
Chapter 02 · Page 14
Beginner

React Event Handling

Handle user events in React with camelCase handlers, synthetic events, and controlled inputs.

TL;DR

  1. 01Use camelCase event names like onClick and onChange in JSX.
  2. 02Pass a function reference as the handler, not a function call.
  3. 03Use e.preventDefault() and e.stopPropagation() to control event flow.

Tips

  1. 01Always use controlled components for forms — it gives you better control over input values and validation.

Warnings

  1. 01Remember to call preventDefault() on forms to prevent page reload on submission.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Event Handling
Chapter 02 · Page 15
Beginner

React Event Handling

(continued)

Basic Event Handlers

  • Attach events with camelCase attribute names like onClick, not lowercase onclick.
    function Button() {
      function handleClick() {
        console.log("Button clicked");
      }
    
      return <button onClick={handleClick}>Click me</button>;
    }
    
  • Pass a function reference, not a function call, or it runs on every render.
    // Good: pass the function
    <button onClick={handleClick}>Click</button>
    
    // Bad: calls function immediately on render
    <button onClick={handleClick()}>Click</button>
    
  • Write inline handlers as arrow functions for short, one-off logic.
    <button onClick={() => console.log("clicked")}>Click</button>
    
  • Pass extra arguments to a handler by wrapping the call in an arrow function.
    function List({ items }) {
      return (
        <ul>
          {items.map(item => (
            <li key={item.id}>
              <button onClick={() => handleDelete(item.id)}>
                Delete {item.name}
              </button>
            </li>
          ))}
        </ul>
      );
    }
    
  • Name handler functions with a handle prefix so their purpose is obvious at a glance.
    function handleDelete(id) { /* ... */ }
    function handleToggle() { /* ... */ }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Event Handling
Chapter 02 · Page 16
Beginner

React Event Handling

(continued)

Common Event Handlers

  • Handle form submission with onSubmit on the <form> element, not onClick on the button.
    function LoginForm() {
      const handleSubmit = (e) => {
        e.preventDefault();
        console.log("Form submitted");
      };
    
      return <form onSubmit={handleSubmit}></form>;
    }
    
  • Handle input changes with onChange, reading the new value from e.target.value.
    function NameInput() {
      const [name, setName] = useState("");
    
      return <input value={name} onChange={(e) => setName(e.target.value)} />;
    }
    
  • Handle mouse events like onMouseEnter, onMouseLeave, and onMouseMove for hover effects.
    <div onMouseEnter={() => setHovered(true)}>
      Hover me
    </div>
    
  • Handle keyboard shortcuts with onKeyDown, checking e.key for the pressed key.
    function SearchBox() {
      const handleKeyDown = (e) => {
        if (e.key === "Enter") submitSearch();
        if (e.key === "Escape") clearSearch();
      };
    
      return <input onKeyDown={handleKeyDown} placeholder="Search..." />;
    }
    
  • Handle focus and blur events with onFocus and onBlur for accessibility cues.
    function Input() {
      const [focused, setFocused] = useState(false);
    
      return (
        <input
          className={focused ? "focused" : ""}
          onFocus={() => setFocused(true)}
          onBlur={() => setFocused(false)}
        />
      );
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Event Handling
Chapter 02 · Page 17
Beginner

React Event Handling

(continued)

Event Object

  • Access event properties like target and preventDefault.
    function handleClick(event) {
      console.log(event.target);  // the element clicked
      console.log(event.type);    // "click"
    }
    
    function handleSubmit(event) {
      event.preventDefault();     // stop form submission
      event.stopPropagation();    // stop event bubbling
    }
  • React's SyntheticEvent wraps the browser's native event for cross-browser consistency; access the underlying event with e.nativeEvent.
    function handleClick(e) {
      console.log(e.nativeEvent); // the underlying browser Event
    }
  • As of React 17, synthetic events are no longer pooled and reused: each event object is freshly created and stays valid for as long as you hold a reference to it, even after the handler returns. Calling e.persist() is now a no-op kept only for backward compatibility — you never need to call it.
    // React 16 and earlier nulled out event fields after the handler ran,
    // so reading e.target.value async required e.persist() first.
    // React 17+: the event object is never pooled, so this just works.
    function handleChange(e) {
      setTimeout(() => console.log(e.target.value), 1000);
    }
  • Get input values with e.target.value.
    const handleChange = (e) => {
      setName(e.target.value);
    };
  • Read checkbox state with e.target.checked.
    function Checkbox() {
      const [checked, setChecked] = useState(false);
      
      return (
        <input
          type="checkbox"
          checked={checked}
          onChange={(e) => setChecked(e.target.checked)}
        />
      );
    }
  • Get the key pressed from keyboard events with e.key.
    function handleKeyDown(e) {
      console.log(e.key);    // "Enter", "Escape", "ArrowUp"
      console.log(e.code);   // "Enter", "Escape", "ArrowUp"
      console.log(e.ctrlKey); // true if Ctrl held
    }
  • Access the current target when events bubble up.
    function handleClick(e) {
      console.log(e.target);        // element that was clicked
      console.log(e.currentTarget); // element with the handler
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Event Handling
Chapter 02 · Page 18
Beginner

React Event Handling

(continued)

Controlled vs Uncontrolled Components

  • Controlled components: React manages the input value.
    function TextInput() {
      const [value, setValue] = useState("");
      
      return (
        <input 
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
      );
    }
  • Uncontrolled components: DOM manages the input.
    function TextInput() {
      const inputRef = useRef();
      
      function handleSubmit() {
        console.log(inputRef.current.value);
      }
      
      return (
        <>
          <input ref={inputRef} />
          <button onClick={handleSubmit}>Submit</button>
        </>
      );
    }
  • Use defaultValue for uncontrolled inputs with an initial value.
    // defaultValue sets the initial value but doesn't control updates
    <input defaultValue="Alice" ref={inputRef} />
  • Controlled inputs enable real-time validation and formatting.
    function PhoneInput() {
      const [phone, setPhone] = useState("");
      
      const handleChange = (e) => {
        // Format as user types: 555-123-4567
        const digits = e.target.value.replace(/\D/g, "");
        setPhone(digits.replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3"));
      };
      
      return <input value={phone} onChange={handleChange} />;
    }
  • Prefer controlled components for forms that need validation.
    // Controlled: easy to read and validate before submit
    const isValid = email.includes("@") && password.length >= 8;
    <button disabled={!isValid}>Submit</button>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Event Handling
Chapter 02 · Page 19
Beginner

React Event Handling

(continued)

Event Delegation

  • Use event delegation for lists of items.
    function List({ items }) {
      const handleItemClick = (e) => {
        if (e.target.dataset.id) {
          console.log("Item clicked:", e.target.dataset.id);
        }
      };
      
      return (
        <ul onClick={handleItemClick}>
          {items.map(item => (
            <li key={item.id} data-id={item.id}>
              {item.name}
            </li>
          ))}
        </ul>
      );
    }
  • More efficient than adding listeners to each item.
  • Use data-* attributes to identify clicked elements.
  • Use e.target.closest() to find the nearest matching ancestor.
    function handleClick(e) {
      const row = e.target.closest("[data-row-id]");
      if (row) {
        console.log("Row clicked:", row.dataset.rowId);
      }
    }
  • Stop bubbling with e.stopPropagation() to prevent parent handlers.
    function Card() {
      return (
        <div onClick={handleCardClick}>
          Card content
          <button onClick={(e) => {
            e.stopPropagation(); // don't trigger handleCardClick
            handleDeleteClick();
          }}>
            Delete
          </button>
        </div>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Event Handling
Chapter 02 · Page 20
Beginner

React Event Handling

(FAQ)

FAQ

You're likely calling the function instead of passing a reference — write onClick={handleClick} not onClick={handleClick()}. If you need to pass arguments, wrap it in an arrow function: onClick={() => handleClick(id)}.

Use e.target.value inside your handler function, e.g. onChange={(e) => setValue(e.target.value)}. For checkboxes, use e.target.checked instead.

preventDefault stops the browser's default behavior (like a form submitting or a link navigating), while stopPropagation stops the event from bubbling up to parent elements. You often need both independently depending on what you're trying to block.

Uncontrolled components (using ref) are useful for file inputs or when integrating with non-React code, since file inputs can't be controlled by React state. For most form fields, controlled components give you real-time access to values for validation and conditional logic.

Add a name attribute to each input and use e.target.name in one handler to update the correct state key: setState(prev => ({ ...prev, [e.target.name]: e.target.value })). This avoids writing a separate handler for every field.

Useful Cheatsheetsusefulcheatsheets.com
React Forms Handling
Chapter 03 · Page 21
Beginner

React Forms Handling

Build and manage controlled forms in React with inputs, validation, and submit handlers.

TL;DR

  1. 01Create controlled inputs by storing value in state.
  2. 02Update state on every keystroke with onChange handlers.
  3. 03Validate and submit forms with a form submit event.

Tips

  1. 01Use the <code>name</code> attribute on inputs to build a generic change handler that works for any number of fields without repetition.

Warnings

  1. 01Always call <code>e.preventDefault()</code> in the submit handler to stop the browser from reloading the page.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Forms Handling
Chapter 03 · Page 22
Beginner

React Forms Handling

(continued)

Controlled Inputs

  • Create a controlled input by storing the value in state.
    const [email, setEmail] = useState("");
    <input
      value={email}
      onChange={(e) => setEmail(e.target.value)}
    />
  • The input value stays in sync with the state at all times.
  • React is the single source of truth for the input's value.
  • Every keystroke updates state, which updates the input display.
  • Controlled inputs let you validate and transform input before storing.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Forms Handling
Chapter 03 · Page 23
Beginner

React Forms Handling

(continued)

Multiple Inputs

  • Store form data in a single state object to keep it organized.
    const [form, setForm] = useState({ name: "", email: "" });
    const handleChange = (e) => {
      const { name, value } = e.target;
      setForm(prev => ({ ...prev, [name]: value }));
    };
  • Use the name attribute to identify which input changed.
  • Spread the previous state and update only the changed field.
  • This pattern scales well as forms grow with more fields.
  • Consider moving to a form library for very large forms.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Forms Handling
Chapter 03 · Page 24
Beginner

React Forms Handling

(continued)

Checkboxes and Selects

  • For checkboxes, use checked instead of value in state.
    const [agree, setAgree] = useState(false);
    <input
      type="checkbox"
      checked={agree}
      onChange={(e) => setAgree(e.target.checked)}
    />
  • For select dropdowns, store the selected value in state.
    const [role, setRole] = useState("user");
    <select value={role} onChange={(e) => setRole(e.target.value)}>
      <option value="user">User</option>
      <option value="admin">Admin</option>
    </select>
  • Handle radio buttons like checkboxes but with a group of options.
  • Textareas work like text inputs with the value attribute.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Forms Handling
Chapter 03 · Page 25
Beginner

React Forms Handling

(continued)

Validation

  • Validate input on every change or only when the user leaves the field.
    const [email, setEmail] = useState("");
    const [error, setError] = useState("");
    const handleChange = (e) => {
      const value = e.target.value;
      setEmail(value);
      if (!value.includes("@")) setError("Invalid email");
      else setError("");
    };
  • Show validation errors near the invalid input for quick feedback.
  • Disable the submit button while there are validation errors.
    <button disabled={error}>Submit</button>
  • Use a form library like React Hook Form for complex validation rules.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Forms Handling
Chapter 03 · Page 26
Beginner

React Forms Handling

(continued)

Form Submission

  • Prevent the default form submission and handle it with JavaScript.
    const handleSubmit = (e) => {
      e.preventDefault();
      console.log(form);
    };
    <form onSubmit={handleSubmit}>
      {/* inputs here */}
      <button type="submit">Submit</button>
    </form>
  • Access form data from state and send it to an API.
  • Show loading and success states while submitting.
  • Reset the form after successful submission using setForm({}).
  • Handle errors gracefully and display them to the user.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Forms Handling
Chapter 03 · Page 27
Beginner

React Forms Handling

(FAQ)

FAQ

Add a name attribute to each input that matches the corresponding state key, then use a single handler: setState(prev => ({ ...prev, [e.target.name]: e.target.value })). This one function scales to any number of fields.

You're missing e.preventDefault() at the top of your submit handler. Without it, the browser performs its default form submission, which reloads the page and wipes your component state.

Checkboxes use e.target.checked instead of e.target.value. In a shared handler, check e.target.type === 'checkbox' and use the appropriate property: type === 'checkbox' ? e.target.checked : e.target.value.

Validate on submit for simple forms to avoid showing errors before the user finishes typing. For better UX, validate on onBlur (when the field loses focus) so errors appear after the user leaves a field rather than mid-input.

A controlled input stores its value in React state and reads it back via the value prop, making React the single source of truth. An uncontrolled input stores its value in the DOM and is read via a ref; controlled inputs are preferred because they make validation and dynamic updates straightforward.

Useful Cheatsheetsusefulcheatsheets.com
React Fragment and Keys
Chapter 04 · Page 28
Beginner

React Fragment and Keys

Master React.Fragment, short syntax, and when keys are necessary.

TL;DR

  1. 01Use Fragment to group elements without adding a wrapper div.
  2. 02Use the <></> short syntax for cleaner code.
  3. 03Fragments cannot have keys unless using React.Fragment explicitly.

Tips

  1. 01Use the <></> short syntax by default for cleaner code, but switch to React.Fragment when you need the key prop.

Warnings

  1. 01Fragments don't support className, id, or other attributes — use a div if you need to style the wrapper.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Fragment and Keys
Chapter 04 · Page 29
Beginner

React Fragment and Keys

(continued)

Fragment Basics

  • Use React.Fragment to group multiple elements without a wrapper.
    function List() {
      return (
        <React.Fragment>
          <h2>Title</h2>
          <p>Description</p>
        </React.Fragment>
      );
    }
  • Fragments don't create extra DOM nodes.
    // Without Fragment: creates a div
    <div>
      <Header />
      <Content />
      <Footer />
    </div>
    
    // With Fragment: no extra node
    <React.Fragment>
      <Header />
      <Content />
      <Footer />
    </React.Fragment>
  • Return multiple elements from a component.
    function Profile() {
      return (
        <React.Fragment>
          <UserHeader />
          <UserContent />
          <UserFooter />
        </React.Fragment>
      );
    }
  • Use fragments in table rows to avoid invalid HTML.
    function TableRow({ item }) {
      return (
        <React.Fragment>
          <td>{item.name}</td>
          <td>{item.price}</td>
          <td>{item.stock}</td>
        </React.Fragment>
      );
    }
  • Fragments are required when a component must return multiple siblings.
    // JSX requires a single root — Fragment is the clean solution
    function NavItems() {
      return (
        <>
          <li><a href="/home">Home</a></li>
          <li><a href="/about">About</a></li>
          <li><a href="/contact">Contact</a></li>
        </>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Fragment and Keys
Chapter 04 · Page 30
Beginner

React Fragment and Keys

(continued)

Short Syntax

  • Use <></> as shorthand for React.Fragment.
    function List() {
      return (
        <>
          <h2>Title</h2>
          <p>Description</p>
        </>
      );
    }
  • Short syntax is cleaner and more readable.
  • Most projects use <></> instead of React.Fragment.
    // Preferred: short syntax
    <>
      <Component />
    </>
    
    // Also works: full syntax
    <React.Fragment>
      <Component />
    </React.Fragment>
  • Short syntax works in any JSX context.
    function App() {
      return (
        <main>
          <>
            <Sidebar />
            <Content />
          </>
        </main>
      );
    }
  • Use <></> for conditional rendering of multiple elements.
    function Status({ isLoggedIn }) {
      return isLoggedIn ? (
        <>
          <WelcomeMessage />
          <UserMenu />
        </>
      ) : (
        <>
          <LoginPrompt />
          <SignupLink />
        </>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Fragment and Keys
Chapter 04 · Page 31
Beginner

React Fragment and Keys

(continued)

Fragment with Keys

  • Only React.Fragment supports the key prop.
    function List({ items }) {
      return (
        <>
          {items.map(item => (
            <React.Fragment key={item.id}>
              <dt>{item.name}</dt>
              <dd>{item.description}</dd>
            </React.Fragment>
          ))}
        </>
      );
    }
  • Use keys when rendering multiple elements per item.
    // Without key: React can't track which elements belong together
    items.map(item => (
      <React.Fragment key={item.id}>
        <Header title={item.title} />
        <Body content={item.content} />
      </React.Fragment>
    ))
  • Short syntax <></> doesn't support keys.
    // This won't work:
    <>
      {items.map(item => (
        <>
          <p key={item.id}>{item.name}</p>
        </>
      ))}
    </>
  • Place the key on React.Fragment, not on child elements.
    // Wrong: key on child, not on fragment
    {items.map(item => (
      <React.Fragment>
        <p key={item.id}>{item.name}</p>
      </React.Fragment>
    ))}
    
    // Correct: key on the fragment itself
    {items.map(item => (
      <React.Fragment key={item.id}>
        <p>{item.name}</p>
      </React.Fragment>
    ))}
  • Keys must be unique among siblings, not globally unique.
    // These keys are fine — they're in separate lists
    listA.map(item => <React.Fragment key={item.id}>...</React.Fragment>)
    listB.map(item => <React.Fragment key={item.id}>...</React.Fragment>)
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Fragment and Keys
Chapter 04 · Page 32
Beginner

React Fragment and Keys

(continued)

Use Cases

  • Return multiple elements from a component.
    function DialogBox() {
      return (
        <>
          <Modal.Header>Confirm Action</Modal.Header>
          <Modal.Body>Are you sure?</Modal.Body>
          <Modal.Footer>
            <button>Cancel</button>
            <button>Confirm</button>
          </Modal.Footer>
        </>
      );
    }
  • Wrap elements for styling without extra divs.
    return (
      <>
        <style>{`
          span { color: red; }
        `}</style>
        <span>Styled text</span>
      </>
    );
  • Group related elements in definition lists.
    function DefinitionList({ terms }) {
      return (
        <dl>
          {terms.map(term => (
            <React.Fragment key={term.id}>
              <dt>{term.word}</dt>
              <dd>{term.definition}</dd>
            </React.Fragment>
          ))}
        </dl>
      );
    }
  • Group table cells when building flexible table row components.
    function ProductRow({ product }) {
      return (
        <tr>
          <React.Fragment>
            <td>{product.name}</td>
            <td>{product.category}</td>
            <td>${product.price}</td>
          </React.Fragment>
        </tr>
      );
    }
  • Avoid invalid HTML nesting by wrapping list items in fragments.
    function NavGroup({ label, links }) {
      return (
        <>
          <li className="group-label">{label}</li>
          {links.map(link => (
            <li key={link.href}>
              <a href={link.href}>{link.text}</a>
            </li>
          ))}
        </>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Fragment and Keys
Chapter 04 · Page 33
Beginner

React Fragment and Keys

(continued)

Common Mistakes

  • Don't use <></> when you need a key on items.
    // Wrong: loses key benefit
    {items.map(item => (
      <>
        <p key={item.id}>{item.name}</p>
      </>
    ))}
    
    // Correct: use React.Fragment with key
    {items.map(item => (
      <React.Fragment key={item.id}>
        <p>{item.name}</p>
      </React.Fragment>
    ))}
  • Don't add classes or other attributes to fragments.
    // Wrong: fragments don't support attributes
    <> className="wrapper">
      <Content />
    </>
    
    // Correct: use a div if you need attributes
    <div className="wrapper">
      <Content />
    </div>
  • Don't forget to import React when using the full syntax.
    // Required for React.Fragment in older setups
    import React from "react";
    
    // Not required for short syntax — handled by JSX transform
    <>...</>
  • Don't use index as key if items can be reordered or removed.
    // Bad: index as key causes issues when list order changes
    {items.map((item, index) => (
      <React.Fragment key={index}>...</React.Fragment>
    ))}
    
    // Good: stable unique ID
    {items.map(item => (
      <React.Fragment key={item.id}>...</React.Fragment>
    ))}
  • Don't nest fragments unnecessarily — keep JSX clean.
    // Unnecessary nesting
    <>
      <>
        <p>Hello</p>
      </>
    </>
    
    // Clean: single fragment
    <>
      <p>Hello</p>
    </>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Fragment and Keys
Chapter 04 · Page 34
Beginner

React Fragment and Keys

(FAQ)

FAQ

Use React.Fragment when you need to pass a key prop, such as when rendering a list of fragments. The short syntax <></> doesn't accept any props, so explicit React.Fragment is the only option in those cases.

No, Fragments don't support any DOM attributes like className, id, or style. If you need to style the wrapper element, use a div or another semantic HTML element instead.

Map over your data and return React.Fragment with a key prop for each item: items.map(item => <React.Fragment key={item.id}>...</React.Fragment>). This avoids wrapper divs while satisfying React's key requirement for lists.

No, Fragments are invisible in the DOM — they leave no trace in the rendered HTML. Your flex or grid container will see the Fragment's children as direct children, which is exactly why Fragments are useful for avoiding layout-breaking wrapper divs.

Fragments themselves don't affect render order, so if children appear out of order the issue is elsewhere — typically conditional rendering logic or an unsorted data source. Check the order of expressions inside your Fragment and the data being mapped.

Useful Cheatsheetsusefulcheatsheets.com
React Keys and Lists
Chapter 05 · Page 35
Beginner

React Keys and Lists

Render lists efficiently with the key prop, understand reconciliation, and avoid common list rendering bugs.

TL;DR

  1. 01Always provide a key prop when rendering lists of items.
  2. 02Use stable, unique identifiers — not array indices.
  3. 03Keys help React identify which items changed for efficient updates.

Tips

  1. 01Always use a unique identifier from your data as the key, preferably an ID from a database or UUID library.

Warnings

  1. 01Using array indices as keys causes bugs when lists are reordered, filtered, or items are added/removed dynamically.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Keys and Lists
Chapter 05 · Page 36
Beginner

React Keys and Lists

(continued)

Why Keys Matter

  • Keys help React identify which items have changed, been added, or removed.
    const items = [
      { id: 1, name: "Alice" },
      { id: 2, name: "Bob" }
    ];
    
    {items.map(item => (
      <div key={item.id}>{item.name}</div>
    ))}
  • Without keys, React assumes items are in the same position.
  • This causes state to persist incorrectly between items.
    // Without keys: input state follows the position, not the person
    {people.map((person, index) => (
      <div key={index}>
        <input defaultValue={person.name} />
      </div>
    ))}
  • Keys ensure component state stays with the correct item.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Keys and Lists
Chapter 05 · Page 37
Beginner

React Keys and Lists

(continued)

Choosing Good Keys

  • Use stable, unique identifiers from your data.
    // Good: unique ID
    {items.map(item => (
      <div key={item.id}>{item.name}</div>
    ))}
  • Avoid array indices as keys, especially for dynamic lists.
    // Bad: index changes when items are reordered
    {items.map((item, index) => (
      <div key={index}>{item.name}</div>
    ))}
  • Use IDs from a database or UUID library for reliable keys.
    import { v4 as uuidv4 } from "uuid";
    
    const newItem = { id: uuidv4(), name: "Charlie" };
  • Keys don't need to be globally unique, just unique within the list.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Keys and Lists
Chapter 05 · Page 38
Beginner

React Keys and Lists

(continued)

Common Mistakes

  • Don't use array indices when the list can be reordered.
    // Problem: if items are sorted, indices change
    const sorted = items.sort((a, b) => a.name.localeCompare(b.name));
    sorted.map((item, i) => <Item key={i} />); // Bad!
  • Don't generate keys on the fly with random values.
    // Bad: new key generated on every render
    items.map(item => (
      <div key={Math.random()}>{item.name}</div>
    ))
  • Don't use complex objects as keys — use simple strings or numbers.
    // Bad: object reference changes
    {items.map(item => (
      <div key={item}>{item.name}</div>
    ))}
  • Always provide a key prop when rendering with map.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Keys and Lists
Chapter 05 · Page 39
Beginner

React Keys and Lists

(continued)

List Rendering Patterns

  • Render a simple list with stable keys.
    export default function UserList({ users }) {
      return (
        <ul>
          {users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      );
    }
  • Handle empty lists gracefully.
    if (users.length === 0) {
      return <p>No users found</p>;
    }
    
    return (
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    );
  • Filter and sort before rendering, not in the map.
    const active = users.filter(u => u.isActive);
    const sorted = active.sort((a, b) => a.name.localeCompare(b.name));
    
    return sorted.map(user => (
      <UserItem key={user.id} user={user} />
    ));
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Keys and Lists
Chapter 05 · Page 40
Beginner

React Keys and Lists

(continued)

Performance Considerations

  • Keys allow React to reuse DOM elements and preserve component state.
    // With proper keys, React updates only changed items
    {items.map(item => (
      <Item key={item.id} data={item} />
    ))}
  • Avoid creating new components inside map — extract to separate component.
    // Bad: defines component inside map
    items.map(item => {
      const Component = createComponent(item);
      return <Component key={item.id} />;
    })
    
    // Good: use a wrapper component
    items.map(item => (
      <ItemWrapper key={item.id} item={item} />
    ))
  • Use React.memo on list items to avoid unnecessary re-renders.
    const ListItem = React.memo(({ item }) => (
      <div>{item.name}</div>
    ));
    
    items.map(item => (
      <ListItem key={item.id} item={item} />
    ))
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Keys and Lists
Chapter 05 · Page 41
Beginner

React Keys and Lists

(FAQ)

FAQ

React uses keys to track which items changed, were added, or removed between renders. Without keys, React falls back to index-based diffing, which can cause incorrect component reuse and state bugs when the list changes.

No — keys generated at render time (like Math.random()) change every render, forcing React to unmount and remount every list item each time. This destroys component state and tanks performance.

An index-based key shifts when items are reordered or deleted, causing React to match the wrong component to the wrong data. A stable ID (like a database primary key) always points to the same item regardless of position.

No — keys only need to be unique among siblings in the same list. The same key value can appear in a completely different list without conflict.

Use a library like uuid or nanoid to assign a stable ID when the item is first created (e.g., on user input), and store that ID with the data. Never generate the key inline during render — generate it once and persist it.

Useful Cheatsheetsusefulcheatsheets.com
React Props and Children
Chapter 06 · Page 42
Beginner

React Props and Children

Master prop drilling, children, and patterns for composable component APIs.

TL;DR

  1. 01Pass data to children using props in a parent component.
  2. 02Use children to accept JSX from parent components.
  3. 03Avoid prop drilling by using Context or composition patterns.

Tips

  1. 01Use composition and Context instead of drilling props through many levels — it makes code cleaner and easier to maintain.

Warnings

  1. 01Don't pass too many props to a single component — it's a sign to break it into smaller, more focused components.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Props and Children
Chapter 06 · Page 43
Beginner

React Props and Children

(continued)

Basic Props

  • Pass data to child components using props as an object.
    function Greeting({ name, age }) {
      return <p>Hello {name}, age {age}</p>;
    }
    
    <Greeting name="Alice" age={30} />
  • Destructure props for cleaner code in function components.
    function Button({ label, onClick, disabled = false }) {
      return (
        <button onClick={onClick} disabled={disabled}>
          {label}
        </button>
      );
    }
  • Use default values for optional props.
    function Card({ title = "Untitled", content }) {
      return <div><h2>{title}</h2><p>{content}</p></div>;
    }
  • Pass functions as props to handle events from children.
    <Button onClick={() => handleClick()} label="Click me" />
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Props and Children
Chapter 06 · Page 44
Beginner

React Props and Children

(continued)

Using Children

  • Accept JSX as children to create flexible wrapper components.
    function Card({ children, title }) {
      return (
        <div className="card">
          <h2>{title}</h2>
          <div className="content">{children}</div>
        </div>
      );
    }
    
    <Card title="Welcome">
      <p>This is card content</p>
    </Card>
  • Children is a special prop that contains nested JSX.
  • Multiple children are passed automatically as an array.
    <Container>
      <Header />
      <Main />
      <Footer />
    </Container>
  • Use React.Children.map to iterate over children.
    function Row({ children }) {
      return (
        <tr>
          {React.Children.map(children, (child, index) => (
            <td key={index}>{child}</td>
          ))}
        </tr>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Props and Children
Chapter 06 · Page 45
Beginner

React Props and Children

(continued)

Prop Drilling and Solutions

  • Prop drilling occurs when passing props through many levels.
    // Level 1
    <App user={user}>
      // Level 2
      <Layout user={user}>
        // Level 3
        <Sidebar user={user}>
          // Level 4
          <Profile user={user} />
        </Sidebar>
      </Layout>
    </App>
  • Use Context to avoid prop drilling for shared data.
    const UserContext = createContext();
    
    <UserContext.Provider value={user}>
      <App />
    </UserContext.Provider>
    
    function Profile() {
      const user = useContext(UserContext);
      return <div>{user.name}</div>;
    }
  • Use composition to pass components instead of data.
    <Layout sidebar={<Sidebar />}>
      <Main />
    </Layout>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Props and Children
Chapter 06 · Page 46
Beginner

React Props and Children

(continued)

Component Composition Patterns

  • Build flexible layouts by accepting components as props.
    function Page({ header: Header, content: Content, footer: Footer }) {
      return (
        <>
          <Header />
          <Content />
          <Footer />
        </>
      );
    }
    
    <Page
      header={<Header />}
      content={<MainContent />}
      footer={<Footer />}
    />
  • Use the render prop pattern for dynamic content.
    function DataFetcher({ render }) {
      const [data, setData] = useState(null);
      useEffect(() => { fetchData().then(setData); }, []);
      return render(data);
    }
    
    <DataFetcher render={(data) => <List items={data} />} />
  • Compose small, focused components for reusability.
    <Modal>
      <Modal.Header>Title</Modal.Header>
      <Modal.Body>Content</Modal.Body>
      <Modal.Footer>Actions</Modal.Footer>
    </Modal>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Props and Children
Chapter 06 · Page 47
Beginner

React Props and Children

(continued)

Advanced Patterns

  • Clone and modify children with cloneElement.
    function FormFields({ children, error }) {
      return React.Children.map(children, (child) =>
        React.cloneElement(child, { error })
      );
    }
  • Use children as a function for render props.
    function Toggle({ children }) {
      const [open, setOpen] = useState(false);
      return children({ open, toggle: () => setOpen(!open) });
    }
    
    <Toggle>
      {({ open, toggle }) => (
        <button onClick={toggle}>
          {open ? "Close" : "Open"}
        </button>
      )}
    </Toggle>
  • Spread remaining props to avoid manual forwarding.
    function Button({ label, ...rest }) {
      return <button {...rest}>{label}</button>;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Props and Children
Chapter 06 · Page 48
Beginner

React Props and Children

(FAQ)

FAQ

Define the function in the parent component and pass it like any other prop: <Button onClick={handleClick} />. In the child, call it via props.onClick() or destructure it directly.

props.children is implicitly populated by JSX between opening and closing tags, while a named slot prop (e.g., header={<Header />}) must be passed explicitly as an attribute. Use named slots when you need multiple distinct content areas in a component.

Use React Context to provide values deep in the tree without threading props through intermediate components, or restructure with composition — pass the deeply-needed component directly as a prop or children so intermediaries never need to know about it.

Yes: <Component {...props} /> works, but be selective — spreading unknown props onto DOM elements causes warnings and can expose internal state. Destructure what you need and spread only a known rest object, e.g., const { internal, ...rest } = props.

Use destructuring defaults in the function signature: function Card({ title = 'Untitled', size = 'md' }). This is preferred over the legacy Component.defaultProps, which is deprecated in modern React.

Useful Cheatsheetsusefulcheatsheets.com
React useRef Hook
Chapter 07 · Page 49
Beginner

React useRef Hook

Learn useRef fundamentals: DOM access, callback refs, useImperativeHandle, and React 19 ref-as-prop.

TL;DR

  1. 01useRef persists a .current value across renders without re-rendering.
  2. 02React 19 passes ref as a prop — forwardRef is deprecated.
  3. 03useImperativeHandle lets a child component expose a controlled API.

Tips

  1. 01Initialise useRef with the type of value you expect. Pass null for DOM refs and a real initial value (e.g. 0, false) for mutable value refs — this makes TypeScript inference easier.

Warnings

  1. 01A ref mutation inside render is allowed only for the renders counter pattern shown above where you don't use the value for display. Any ref read that feeds JSX output must happen in an effect.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useRef Hook
Chapter 07 · Page 50
Beginner

React useRef Hook

(continued)

useRef Basics

useRef creates a plain JavaScript object with a single .current property. React keeps the same object across every render — mutating .current never schedules a re-render.

  • Attach a ref to a DOM element to call browser methods on it.
    import { useRef } from 'react';
    
    function TextInput() {
      const inputRef = useRef(null);
    
      function handleClick() {
        inputRef.current.focus(); // direct DOM call
      }
    
      return (
        <>
          <input ref={inputRef} placeholder="Type here" />
          <button onClick={handleClick}>Focus input</button>
        </>
      );
    }
  • Store any mutable value — not just DOM nodes. Here a render counter persists without triggering re-renders.
    function RenderCounter() {
      const renders = useRef(0);
      renders.current += 1; // mutate during render is fine for refs
    
      return <p>Rendered {renders.current} times (no re-render loop)</p>;
    }
  • Key distinction: ref vs state.
    • ref.current changes — component does not re-render, UI stays the same.
    • setState changes — component re-renders, UI updates.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useRef Hook
Chapter 07 · Page 51
Beginner

React useRef Hook

(continued)

React 19: Refs as Props

Before React 19, passing a ref into a child component required wrapping it in forwardRef. React 19 removes that requirement — ref is now a plain prop, just like className or onClick.

  • Before (React 18 and earlier) — forwardRef wrapper required:
    // React 18: you had to wrap in forwardRef
    const Input = forwardRef(function Input(props, ref) {
      return <input ref={ref} {...props} />;
    });
    
    function Form() {
      const inputRef = useRef(null);
      return <Input ref={inputRef} placeholder="Email" />;
    }
  • After (React 19) — ref arrives as a plain prop:
    // React 19: ref is just a prop
    function Input({ ref, ...props }) {
      return <input ref={ref} {...props} />;
    }
    
    function Form() {
      const inputRef = useRef(null);
      return <Input ref={inputRef} placeholder="Email" />;
    }
  • Class components still use createRef and are unaffected by this change.
  • forwardRef still works in React 19 for library backward compatibility but shows a deprecation warning in dev mode.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useRef Hook
Chapter 07 · Page 52
Beginner

React useRef Hook

(continued)

Callback Refs

Instead of passing a ref object to the ref prop, you can pass a function. React calls it with the DOM node on mount and null on unmount. This is useful when you need to act on the node the instant it appears — before any effect would fire.

  • Measure an element's size the moment it mounts:
    function MeasuredBox() {
      const [height, setHeight] = useState(null);
    
      const measuredRef = useCallback((node) => {
        if (node !== null) {
          setHeight(node.getBoundingClientRect().height);
        }
      }, []); // stable function — no deps needed
    
      return (
        <>
          <div ref={measuredRef} style={{ padding: 20 }}>
            Resize me
          </div>
          {height !== null && <p>Height: {height}px</p>}
        </>
      );
    }
  • The callback fires again when the element re-mounts (e.g. after conditional rendering), giving you an automatic measurement update — a regular ref object would not trigger this.
  • Wrap the callback in useCallback with a stable dependency array to avoid React re-running the ref function on every render.
  • Clean-up pattern — run code on both attach and detach:
    const logRef = useCallback((node) => {
      if (node) {
        console.log('mounted:', node.tagName);
      } else {
        console.log('unmounted');
      }
    }, []);
    
    return <div ref={logRef}>Watch the console</div>;
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useRef Hook
Chapter 07 · Page 53
Beginner

React useRef Hook

(continued)

useImperativeHandle

useImperativeHandle lets a child component decide exactly which methods a parent can call through a ref. Instead of exposing the raw DOM node, the child publishes a controlled object.

  • Expose focus and clear without leaking the internal input ref:
    // React 19 style — ref as a prop, no forwardRef needed
    function FancyInput({ ref }) {
      const inputRef = useRef(null);
    
      useImperativeHandle(ref, () => ({
        focus() {
          inputRef.current.focus();
        },
        clear() {
          inputRef.current.value = '';
        },
      }));
    
      return <input ref={inputRef} className="fancy" />;
    }
    
    function Form() {
      const fancyRef = useRef(null);
    
      return (
        <>
          <FancyInput ref={fancyRef} />
          <button onClick={() => fancyRef.current.focus()}>Focus</button>
          <button onClick={() => fancyRef.current.clear()}>Clear</button>
        </>
      );
    }
  • Pass a dependency array as the third argument to control when the handle object is recreated:
    useImperativeHandle(ref, () => ({ getValue: () => value }), [value]);
  • Restrict the exposed API intentionally — if you don't expose scrollIntoView, the parent simply cannot call it.
    useImperativeHandle(ref, () => ({
      focus: () => inputRef.current.focus(),
      // scrollIntoView intentionally not exposed
    }));
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useRef Hook
Chapter 07 · Page 54
Beginner

React useRef Hook

(continued)

Best Practices

  • Prefer state over refs for UI values. If a value change should update the screen, use useState — not a ref.
    // Wrong: ref won't update the UI
    const count = useRef(0);
    const increment = () => { count.current++; }; // no re-render
    
    // Right: state drives the UI
    const [count, setCount] = useState(0);
    const increment = () => setCount(c => c + 1);
  • Never read refs during rendering. The DOM node is null until after mount. Reading it during render returns null at best and throws at worst.
    // Wrong: ref is null here
    function Bad() {
      const ref = useRef(null);
      const width = ref.current?.offsetWidth; // always undefined on first render
      return <div ref={ref}>{width}px</div>;
    }
    
    // Right: read in effect or event handler
    function Good() {
      const ref = useRef(null);
      const [width, setWidth] = useState(0);
      useEffect(() => { setWidth(ref.current.offsetWidth); }, []);
      return <div ref={ref}>{width}px</div>;
    }
  • React 19: drop forwardRef in new components. Accept ref as a plain prop — it's simpler and the JSX tools generate better TypeScript types automatically.
  • Use callback refs for dynamic elements. When an element may mount and unmount (e.g. behind a conditional), a callback ref ensures you always run your setup code at the right time.
  • Don't store derived data in refs. Compute it during render instead — refs that shadow state become stale and are hard to debug.
    // Wrong: keeping a ref in sync with state manually
    const doubleRef = useRef(count * 2);
    
    // Right: derive during render
    const double = count * 2;
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useRef Hook
Chapter 07 · Page 55
Beginner

React useRef Hook

(FAQ)

FAQ

useState triggers a re-render when its value changes; useRef does not. Use useState when a value change needs to update the UI. Use useRef when you need to hold onto a DOM node, a timer ID, or any value that should survive re-renders without causing them.

No. In React 19, function components receive ref as a plain prop, so you can write function Input({ ref }) {} directly. forwardRef still works for backward compatibility but is considered legacy and may be removed in a future major version.

A callback ref is a function you pass to the ref prop instead of a ref object. React calls it with the DOM node on mount and with null on unmount. Use it when you need to imperatively measure an element or set up an observer as soon as the node becomes available, especially for dynamically rendered elements.

Use useImperativeHandle when a parent needs to call specific methods on a child component — for example, focus(), reset(), or scroll() — but you want to hide the child's internal DOM node. It lets you publish a minimal, intentional API instead of exposing the raw DOM element.

Refs attached to JSX elements are populated after the component mounts, so ref.current is null during the initial render. Always read ref.current inside a useEffect (which runs after mount) or inside an event handler (which fires after the user interacts with an already-mounted element).

Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 56
Beginner

React useState Hook

Manage React component state with useState to store and update values across renders.

TL;DR

  1. 01Declare state with useState and get back value and setter.
  2. 02Call setter to update state, triggering a re-render.
  3. 03Use functional updates for state based on previous state.

Tips

  1. 01Use functional updates (prev => ...) when the new state depends on the old state — it's safer and more readable.

Warnings

  1. 01Never mutate state directly — always create new objects and arrays to trigger re-renders properly.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 57
Beginner

React useState Hook

(continued)

Basic useState

  • Declare state with initial value.
    const [count, setCount] = useState(0);
  • count is the current value, setCount updates and re-renders.
    function Counter() {
      const [count, setCount] = useState(0);
      
      return (
        <>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>
            Increment
          </button>
        </>
      );
    }
  • Each state variable needs its own useState call.
  • Initialize state with strings, numbers, booleans, arrays, or objects.
    const [name, setName] = useState("");
    const [isOpen, setIsOpen] = useState(false);
    const [items, setItems] = useState([]);
    const [user, setUser] = useState(null);
  • Calling the setter always replaces the value, never merges.
    // Calling setCount replaces the entire value
    setCount(42); // count is now 42, not 42 + oldCount
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 58
Beginner

React useState Hook

(continued)

Multiple State Variables

  • Declare multiple state variables separately.
    function Form() {
      const [name, setName] = useState("");
      const [email, setEmail] = useState("");
      const [age, setAge] = useState(0);
      
      return (
        <>
          <input value={name} onChange={(e) => setName(e.target.value)} />
          <input value={email} onChange={(e) => setEmail(e.target.value)} />
          <input value={age} onChange={(e) => setAge(e.target.value)} />
        </>
      );
    }
  • Group related state into a single object.
    const [form, setForm] = useState({
      name: "",
      email: "",
      age: 0
    });
  • Update a single field in an object with spread syntax.
    const handleChange = (field) => (e) => {
      setForm(prev => ({ ...prev, [field]: e.target.value }));
    };
  • Keep unrelated state variables separate for clarity.
    // These are unrelated — keep them separate
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(null);
    const [data, setData] = useState(null);
  • Use useReducer when multiple variables always update together.
    // If isLoading, error, and data always change at once
    // consider useReducer instead of three useState calls
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 59
Beginner

React useState Hook

(continued)

Functional Updates

  • Use a function to update state based on previous value.
    function Counter() {
      const [count, setCount] = useState(0);
      
      // Direct update (fine for simple cases)
      // setCount(count + 1);
      
      // Functional update (preferred for dependent updates)
      return <button onClick={() => setCount(prev => prev + 1)}>Increment</button>;
    }
  • Functional updates are safer when multiple updates happen fast.
    const handleClick = () => {
      setCount(prev => prev + 1);
      setCount(prev => prev + 1);
      // Both increments use the latest state — count increases by 2
    };
  • Use functional updates inside useEffect and useCallback.
    useEffect(() => {
      const timer = setInterval(() => {
        setCount(prev => prev + 1); // safe inside interval
      }, 1000);
      return () => clearInterval(timer);
    }, []); // no count in deps needed
  • Functional updates work with arrays too.
    function addItem(newItem) {
      setItems(prev => [...prev, newItem]);
    }
    
    function removeItem(id) {
      setItems(prev => prev.filter(item => item.id !== id));
    }
  • Direct updates are fine when the new value doesn't depend on old state.
    // setName doesn't need the previous name
    setName("Alice");
    setIsOpen(true);
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 60
Beginner

React useState Hook

(continued)

Lazy Initialization

  • Initialize state from an expensive computation using a function.
    const [state, setState] = useState(() => {
      return expensiveComputation();
    });
  • Function runs only on mount, not on every render.
    const [todos, setTodos] = useState(() => {
      const saved = localStorage.getItem("todos");
      return saved ? JSON.parse(saved) : [];
    });
  • Useful for loading from localStorage or parsing initial data.
  • Without lazy initialization, the function runs on every render.
    // Bad: expensiveComputation() runs on every render
    const [state, setState] = useState(expensiveComputation());
    
    // Good: arrow function defers the call to mount only
    const [state, setState] = useState(() => expensiveComputation());
  • Pass the function itself, not the result of calling it.
    // Correct: pass a function
    useState(() => computeInitialValue())
    
    // Wrong: calls the function immediately
    useState(computeInitialValue())
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 61
Beginner

React useState Hook

(continued)

Updating Objects and Arrays

  • Create a new object when updating — never mutate state directly.
    const [user, setUser] = useState({ name: "Alice", age: 30 });
    
    // Wrong: mutation — React won't detect the change
    user.age = 31;
    
    // Right: create new object with spread
    setUser({ ...user, age: 31 });
  • Create a new array for all array state operations.
    const [items, setItems] = useState(["a", "b", "c"]);
    
    // Add item
    setItems([...items, "d"]);
    
    // Remove item
    setItems(items.filter(item => item !== "b"));
  • Update a nested object by spreading at every level.
    const [profile, setProfile] = useState({ name: "Alice", address: { city: "NYC" } });
    
    // Update nested field
    setProfile(prev => ({
      ...prev,
      address: { ...prev.address, city: "LA" }
    }));
  • Update a specific item in an array by mapping over it.
    const [tasks, setTasks] = useState([{ id: 1, done: false }]);
    
    function toggleTask(id) {
      setTasks(prev =>
        prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
      );
    }
  • Use the Immer library for complex nested updates.
    import produce from "immer";
    
    setProfile(produce(draft => {
      draft.address.city = "LA"; // direct mutation is safe inside produce
    }));
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 62
Beginner

React useState Hook

(FAQ)

FAQ

Use functional updates whenever the new state depends on the previous state, especially inside event handlers, async callbacks, or effects where the closure may capture a stale value. This guarantees you're working with the latest state, not a snapshot from when the function was created.

Spread the existing object and override only the changed fields: setState(prev => ({ ...prev, name: 'new' })). React requires a new object reference to detect the change, so never do setState(obj.name = 'new') directly.

Yes — declare as many useState calls as you need, one per logical piece of state. Keeping state variables separate (e.g., const [name, setName] and const [age, setAge]) is cleaner and avoids the need to spread on every update compared to storing everything in one object.

Passing a function to useState — useState(() => expensiveCalc()) — tells React to call it only on the initial render instead of every render. Use this when the initial value requires heavy computation, parsing, or reading from localStorage.

Calling array.push() mutates the existing array, so React sees the same reference and skips the re-render. Instead, create a new array: setState(prev => [...prev, newItem]).

Useful Cheatsheetsusefulcheatsheets.com
React Accessibility
Chapter 09 · Page 63
Intermediate

React Accessibility

Build accessible React apps using semantic HTML, ARIA attributes, and keyboard navigation.

TL;DR

  1. 01Use semantic HTML elements to give the browser meaningful structure.
  2. 02Add ARIA attributes only when semantic HTML is not enough.
  3. 03Test every interactive element with keyboard and screen readers.

Tips

  1. 01Use semantic HTML first — it covers 80% of accessibility requirements without any extra ARIA attributes or JavaScript.

Warnings

  1. 01ARIA is not a substitute for semantic HTML — use it to enhance, not replace proper HTML structure.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Accessibility
Chapter 09 · Page 64
Intermediate

React Accessibility

(continued)

Semantic HTML

  • Use semantic elements instead of divs.
    // Bad: divs with no meaning
    <div onClick={() => setOpen(!open)}>Menu</div>
    
    // Good: semantic button element
    <button onClick={() => setOpen(!open)}>Menu</button>
  • Use proper heading hierarchy to structure pages.
    <h1>Main Title</h1>
    <section>
      <h2>Section Title</h2>
      <p>Content</p>
    </section>
  • Use landmark elements for page regions.
    <header>Site Header</header>
    <nav>Navigation Links</nav>
    <main>Page Content</main>
    <footer>Site Footer</footer>
  • Use lists for groups of related items.
    <ul>
      <li><a href="/about">About</a></li>
      <li><a href="/contact">Contact</a></li>
    </ul>
  • Use the button element for any clickable action.
    // Button triggers an action — use <button>
    <button onClick={openModal}>Open Settings</button>
    
    // Link navigates — use <a>
    <a href="/profile">View Profile</a>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Accessibility
Chapter 09 · Page 65
Intermediate

React Accessibility

(continued)

ARIA Attributes

  • Use aria-label when the visible text doesn't describe the element.
    <button aria-label="Close menu">×</button>
    <button aria-label="Delete item Alice">Delete</button>
  • Use aria-live to announce dynamic content to screen readers.
    <div aria-live="polite" aria-atomic="true">
      {statusMessage}
    </div>
  • Use role to define the purpose of a custom element.
    <div role="button" onClick={handleClick} tabIndex="0">
      Custom Button
    </div>
  • Use aria-expanded to indicate open or closed state.
    <button
      aria-expanded={isOpen}
      aria-controls="menu-list"
      onClick={() => setIsOpen(!isOpen)}
    >
      Menu
    </button>
  • Use aria-describedby to link help text to an input.
    <input
      id="password"
      type="password"
      aria-describedby="password-hint"
    />
    <p id="password-hint">Must be at least 8 characters.</p>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Accessibility
Chapter 09 · Page 66
Intermediate

React Accessibility

(continued)

Forms and Labels

  • Link every input to a label using htmlFor and id.
    <label htmlFor="email">Email:</label>
    <input id="email" type="email" />
  • Use specific input types for built-in validation.
    <input type="email" />
    <input type="password" />
    <input type="date" />
  • Show validation errors with aria-invalid and aria-errormessage.
    <input
      id="email"
      type="email"
      aria-invalid={!!error}
      aria-errormessage="email-error"
    />
    {error && <p id="email-error" role="alert">{error}</p>}
  • Mark required fields with required and aria-required.
    <label htmlFor="name">Name <span aria-hidden="true">*</span></label>
    <input id="name" type="text" required aria-required="true" />
  • Use fieldset and legend to group related form controls.
    <fieldset>
      <legend>Notification preferences</legend>
      <label><input type="checkbox" name="email" /> Email</label>
      <label><input type="checkbox" name="sms" /> SMS</label>
    </fieldset>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Accessibility
Chapter 09 · Page 67
Intermediate

React Accessibility

(continued)

Keyboard Navigation

  • Make interactive elements keyboard accessible by default.
    // Buttons and links are keyboard accessible by default
    <button onClick={handleClick}>Click me</button>
    <a href="/next">Next page</a>
  • Use tabIndex="0" to make custom elements focusable.
    <div
      role="button"
      tabIndex="0"
      onClick={handleClick}
      onKeyDown={(e) => e.key === "Enter" && handleClick()}
    >
      Custom Button
    </div>
  • Handle both Enter and Space for custom button elements.
    function handleKeyDown(e) {
      if (e.key === "Enter" || e.key === " ") {
        e.preventDefault();
        handleClick();
      }
    }
  • Trap focus inside modals when they are open.
    function Modal({ isOpen, onClose, children }) {
      const firstFocusRef = useRef(null);
      
      useEffect(() => {
        if (isOpen) firstFocusRef.current?.focus();
      }, [isOpen]);
      
      return isOpen ? (
        <div role="dialog" aria-modal="true">
          <button ref={firstFocusRef} onClick={onClose}>Close</button>
          {children}
        </div>
      ) : null;
    }
  • Use tabIndex="-1" to remove elements from tab order.
    // Remove decorative icon from keyboard navigation
    <span tabIndex="-1" aria-hidden="true">★</span>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Accessibility
Chapter 09 · Page 68
Intermediate

React Accessibility

(continued)

Screen Reader Testing

  • Test with built-in screen readers on each platform.
    macOS: VoiceOver (Cmd + F5)
    Windows: NVDA (free download at nvaccess.org)
    iOS: VoiceOver (Settings > Accessibility)
    Android: TalkBack (Settings > Accessibility)
  • Use aria-live for dynamic status messages.
    <div aria-live="polite" aria-atomic="true">
      {loadingMessage}
    </div>
  • Use role="alert" for urgent messages that need immediate attention.
    {error && (
      <div role="alert">
        {error}
      </div>
    )}
  • Hide decorative elements from screen readers with aria-hidden.
    <img src="decorative-bg.png" alt="" aria-hidden="true" />
    <span aria-hidden="true"></span>
  • Use jest-axe to automate accessibility checks in tests.
    import { axe, toHaveNoViolations } from 'jest-axe';
    expect.extend(toHaveNoViolations);
    
    test('has no accessibility violations', async () => {
      const { container } = render(<MyForm />);
      const results = await axe(container);
      expect(results).toHaveNoViolations();
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Accessibility
Chapter 09 · Page 69
Intermediate

React Accessibility

(FAQ)

FAQ

Use ARIA roles only when you cannot use a native HTML element that provides the same semantics — for example, when building a custom dropdown or tab component with div elements. Native elements like button, nav, and input already carry the correct role, so adding redundant ARIA roles on them is unnecessary and can cause conflicts.

Pass an id prop to your input and a matching htmlFor prop (not for) to your label element. For custom components that wrap native inputs, forward the id down to the underlying input element so the browser can establish the association.

Divs are not focusable and not in the tab order by default, so keyboard users cannot reach or activate them. Replace the div with a button element, which gets focus, Enter/Space handling, and the correct role for free — or add tabIndex={0}, onKeyDown handling, and role='button' if you must use a non-semantic element.

Use NVDA (Windows) or VoiceOver (Mac, built-in) paired with your app running locally — navigate through every interactive element using Tab and arrow keys and confirm the announced text matches the visual label. The axe DevTools browser extension can also catch the majority of ARIA and labeling issues automatically during development.

Use aria-label to provide an inline string label when no visible text label exists, such as an icon-only button. Use aria-labelledby when a visible element already contains the label text — point it to that element's id so screen readers read existing content instead of duplicating it in code.

Useful Cheatsheetsusefulcheatsheets.com
React Composition
Chapter 10 · Page 70
Intermediate

React Composition

Master React patterns like compound components, render props, and higher-order components for clean reusable patterns.

TL;DR

  1. 01Use compound components to create flexible component APIs.
  2. 02Use render props to share logic through children functions.
  3. 03Use higher-order components to wrap and enhance existing components.

Tips

  1. 01Use custom hooks instead of render props or HOCs for new code, since hooks are simpler and easier to understand.

Warnings

  1. 01Deeply nested HOCs create hard-to-debug code — prefer flat composition using multiple custom hooks instead.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Composition
Chapter 10 · Page 71
Intermediate

React Composition

(continued)

Compound Components

  • Build components that work together as a group with shared state.
    const Accordion = ({ children }) => {
      const [active, setActive] = useState(null);
      return (
        <AccordionContext.Provider value={{ active, setActive }}>
          {children}
        </AccordionContext.Provider>
      );
    };
    
    const AccordionItem = ({ id, title, children }) => {
      const { active, setActive } = useContext(AccordionContext);
      return (
        <div>
          <button onClick={() => setActive(id)}>{title}</button>
          {active === id && <div>{children}</div>}
        </div>
      );
    };
    
    <Accordion>
      <AccordionItem id="1" title="Section 1">Content 1</AccordionItem>
      <AccordionItem id="2" title="Section 2">Content 2</AccordionItem>
    </Accordion>
  • Compound components are flexible because the child components control their appearance.
  • They share state through Context instead of passing props through every level.
  • Use this pattern for tightly coupled components like form fields and labels.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Composition
Chapter 10 · Page 72
Intermediate

React Composition

(continued)

Render Props

  • Pass a function as a prop to let child components decide what to render.
    const MouseTracker = ({ render }) => {
      const [pos, setPos] = useState({ x: 0, y: 0 });
      
      const handleMouseMove = (e) => {
        setPos({ x: e.clientX, y: e.clientY });
      };
      
      return (
        <div onMouseMove={handleMouseMove}>
          {render(pos)}
        </div>
      );
    };
    
    <MouseTracker render={({ x, y }) => (
      <p>Mouse at {x}, {y}</p>
    )} />
  • The render function receives data and returns JSX to display.
  • This lets you reuse logic without creating a wrapper component.
  • Render props are flexible because the caller decides what to render.
  • The children function is a special render prop pattern.
    <MouseTracker>
      {({ x, y }) => <p>Position: {x}, {y}</p>}
    </MouseTracker>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Composition
Chapter 10 · Page 73
Intermediate

React Composition

(continued)

Higher-Order Components (HOC)

  • Wrap a component to add or modify its behavior.
    const withAuth = (Component) => {
      return (props) => {
        const [user, setUser] = useState(null);
        const [loading, setLoading] = useState(true);
        
        useEffect(() => {
          checkAuth().then(u => {
            setUser(u);
            setLoading(false);
          });
        }, []);
        
        if (loading) return <p>Loading...</p>;
        if (!user) return <p>Not authenticated</p>;
        
        return <Component user={user} {...props} />;
      };
    };
    
    const Dashboard = ({ user }) => <h1>Welcome {user.name}</h1>;
    const ProtectedDashboard = withAuth(Dashboard);
  • HOCs add props, logic, or wrappers to existing components.
  • They're useful for cross-cutting concerns like authentication or theming.
  • Avoid deeply nested HOCs — use composition instead.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Composition
Chapter 10 · Page 74
Intermediate

React Composition

(continued)

When to Use Each Pattern

  • Use compound components when building a cohesive component suite.
    <Form>
      <Form.Field name="email" />
      <Form.Field name="password" />
      <Form.Submit>Login</Form.Submit>
    </Form>
  • Use render props to share logic between unrelated components.
    <DataFetcher url="/api/users" render={data => (
      <UserList users={data} />
    )} />
  • Use HOCs to enhance existing components with logic.
    const enhancedComponent = withDataFetching(MyComponent);
  • Prefer composition over deeply nested patterns for readability.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Composition
Chapter 10 · Page 75
Intermediate

React Composition

(continued)

Modern Alternatives

  • Custom hooks often replace render props and HOCs for sharing logic.
    function useMousePosition() {
      const [pos, setPos] = useState({ x: 0, y: 0 });
      useEffect(() => {
        const handleMove = (e) => setPos({ x: e.clientX, y: e.clientY });
        window.addEventListener('mousemove', handleMove);
        return () => window.removeEventListener('mousemove', handleMove);
      }, []);
      return pos;
    }
    
    function MyComponent() {
      const pos = useMousePosition();
      return <p>Position: {pos.x}, {pos.y}</p>;
    }
  • Custom hooks are simpler and more flexible than render props or HOCs.
  • Use hooks as your first choice for sharing logic in modern React.
  • Composition patterns are still useful for component APIs and layout.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Composition
Chapter 10 · Page 76
Intermediate

React Composition

(FAQ)

FAQ

Use compound components when you want a flexible, declarative API where parent and child components share implicit state (like a Tabs/Tab pair). Use render props when you need to inject dynamic content or logic into a single component that consumers control directly.

Extract the logic into a custom hook and call it in each component that needs it. This is the modern recommended approach — it avoids wrapper components entirely and keeps the component tree flat.

An HOC wraps a component and returns a new one, adding behavior at the component level; it shows up in the React tree and can obscure the component hierarchy. A custom hook shares logic at the function level without adding any components to the tree, making it easier to trace and debug.

Wrapper hell happens when multiple HOCs are stacked around a single component, making the component tree and props difficult to follow. Refactor each HOC's logic into a separate custom hook and call them directly inside the component instead.

Use React context inside the compound component — the parent holds the shared state and provides it via a context, and each child reads from that context directly. This keeps the public API clean while avoiding the need to pass props through every intermediate child.

Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 77
Intermediate

React Custom Hooks

Build reusable custom hooks to extract component logic and share stateful behavior.

TL;DR

  1. 01Extract stateful logic from components into reusable hook functions.
  2. 02Start every custom hook name with the word use.
  3. 03Call other hooks inside your hook to compose behavior.

Tips

  1. 01Return values from custom hooks in the same format as built-in hooks — use arrays for positional access or objects for named access.

Warnings

  1. 01Each component that uses a custom hook gets its own isolated state instance — they don't automatically share state between them.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 78
Intermediate

React Custom Hooks

(continued)

Basic Structure

  • Write a custom hook as a regular function that calls React hooks.
    function useToggle(initialValue = false) {
      const [value, setValue] = useState(initialValue);
    
      const toggle = useCallback(() => {
        setValue(v => !v);
      }, []);
    
      return [value, toggle];
    }
    
  • Combine useState and useEffect to track a browser API value over time.
    function useDebouncedValue(value, delayMs = 300) {
      const [debounced, setDebounced] = useState(value);
    
      useEffect(() => {
        const timer = setTimeout(() => setDebounced(value), delayMs);
        return () => clearTimeout(timer); // cancel if value changes again
      }, [value, delayMs]);
    
      return debounced;
    }
    
  • Call your hook from a component just like a built-in hook.
    function SearchBox() {
      const [query, setQuery] = useState('');
      const debouncedQuery = useDebouncedValue(query, 400);
    
      useEffect(() => {
        if (debouncedQuery) searchApi(debouncedQuery);
      }, [debouncedQuery]);
    
      return <input value={query} onChange={e => setQuery(e.target.value)} />;
    }
    
  • Keep each hook focused on a single concern.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 79
Intermediate

React Custom Hooks

(continued)

Naming Rules

  • Always start custom hook names with use, like useToggle.
    // Good: clearly indicates it's a hook
    function useToggle() { }
    function useFetch(url) { }
    function useLocalStorage(key) { }
    
    // Bad: doesn't follow naming convention
    function toggle() { }
    function fetch() { }
  • Use camelCase after the use prefix for consistency.
  • Naming triggers React ESLint rules and warnings.
  • Pick names that describe what the hook does.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 80
Intermediate

React Custom Hooks

(continued)

Common Patterns

  • Build useToggle for managing boolean state.
    function useToggle(initial = false) {
      const [value, setValue] = useState(initial);
      const toggle = () => setValue(!value);
      return [value, toggle];
    }
    
    // Usage
    function Modal() {
      const [isOpen, toggleOpen] = useToggle(false);
      return (
        <>
          <button onClick={toggleOpen}>Open</button>
          {isOpen && <div>Modal content</div>}
        </>
      );
    }
  • Build usePrevious to track previous prop or state values.
    function usePrevious(value) {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      }, [value]);
      return ref.current;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 81
Intermediate

React Custom Hooks

(continued)

Composing and Typing Hooks

  • Compose several built-in hooks inside one custom hook to build a higher-level abstraction.
    function useOnlineStatus() {
      const [isOnline, setIsOnline] = useState(navigator.onLine);
    
      useEffect(() => {
        const goOnline = () => setIsOnline(true);
        const goOffline = () => setIsOnline(false);
        window.addEventListener('online', goOnline);
        window.addEventListener('offline', goOffline);
        return () => {
          window.removeEventListener('online', goOnline);
          window.removeEventListener('offline', goOffline);
        };
      }, []);
    
      return isOnline;
    }
    
  • Build one custom hook on top of another instead of duplicating logic.
    function useSyncedField(key, initialValue) {
      const [value, setValue] = useLocalStorage(key, initialValue); // reuse an existing hook
      const isOnline = useOnlineStatus(); // compose a second hook
      return { value, setValue, isOnline };
    }
    
  • Type a generic custom hook so callers get the correct inferred return type.
    function useToggle<T = boolean>(initial: T): [T, () => void] {
      const [value, setValue] = useState(initial);
      const toggle = useCallback(() => setValue(v => !v as T), []);
      return [value, toggle];
    }
    
    const [isOpen, toggleOpen] = useToggle(false); // isOpen inferred as boolean
    
  • Test a custom hook in isolation with renderHook from @testing-library/react.
    import { renderHook, act } from '@testing-library/react';
    
    test('useToggle flips its value', () => {
      const { result } = renderHook(() => useToggle(false));
      act(() => result.current[1]()); // call the toggle function
      expect(result.current[0]).toBe(true);
    });
    
  • renderHook wraps your hook in a tiny test component so you never need a real UI to test it.
  • Keep composed hooks shallow — two or three layers deep is easier to trace than a long chain.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 82
Intermediate

React Custom Hooks

(continued)

Sharing State Across Components

  • Each component gets its own state instance of a custom hook.
    function useCounter() {
      const [count, setCount] = useState(0);
      return [count, () => setCount(count + 1)];
    }
    
    function Component1() {
      const [count, increment] = useCounter();
      return <button onClick={increment}>{count}</button>;
    }
    
    function Component2() {
      const [count, increment] = useCounter();
      return <button onClick={increment}>{count}</button>;
    }
    // Each component has separate count state
  • Use Context API with custom hooks to share state.
    function useAuth() {
      const context = useContext(AuthContext);
      if (!context) throw new Error("useAuth needs provider");
      return context;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 83
Intermediate

React Custom Hooks

(FAQ)

FAQ

Extract into a custom hook when the same stateful logic (fetching, form handling, subscriptions) appears in multiple components, or when a single component's logic grows complex enough to benefit from separation. Custom hooks let you test and reuse that logic independently of any UI.

No — hooks must be called at the top level of a function, never inside conditionals, loops, or nested functions. This rule applies inside custom hooks too, not just components.

You can't share state via a hook alone — each component calling the hook gets its own independent state instance. To share state, lift it to a common parent and pass it down, or use a context provider that the hook reads from internally.

Return an array (like useState) when consumers will typically rename the values and order is obvious; return an object (like useReducer's dispatch pattern or React Query) when there are many return values or names carry meaning. Mixing both in a single hook adds confusion.

No — a custom hook runs in the context of the component that calls it, so re-render behavior is identical to inlining the logic. The hook abstraction has no runtime overhead.

Useful Cheatsheetsusefulcheatsheets.com
React Error Boundaries
Chapter 12 · Page 84
Intermediate

React Error Boundaries

Catch React errors with error boundaries, handle lifecycle errors, and display fallback UI gracefully.

TL;DR

  1. 01Create a class component with getDerivedStateFromError to catch errors.
  2. 02Display fallback UI when errors are caught instead of a blank page.
  3. 03Log errors for debugging and monitoring in production.

Tips

  1. 01Use error boundaries at the page level and around features to keep the rest of your app working when something breaks.

Warnings

  1. 01Error boundaries only catch errors during rendering, not in event handlers or async code — use try-catch for those.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Error Boundaries
Chapter 12 · Page 85
Intermediate

React Error Boundaries

(continued)

Creating Error Boundaries

  • Create a class component with error lifecycle methods.
    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false };
      }
      
      static getDerivedStateFromError(error) {
        return { hasError: true };
      }
      
      render() {
        if (this.state.hasError) {
          return <h1>Something went wrong.</h1>;
        }
        return this.props.children;
      }
    }
  • Error boundaries only work with class components, not functions.
  • Use getDerivedStateFromError to update state when an error occurs.
  • This catches errors in the component tree below the boundary.
  • Error boundaries must return a fallback UI or null.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Error Boundaries
Chapter 12 · Page 86
Intermediate

React Error Boundaries

(continued)

Logging and Debugging

  • Use componentDidCatch to log errors for debugging.
    class ErrorBoundary extends React.Component {
      componentDidCatch(error, errorInfo) {
        console.error("Error caught:", error);
        console.error("Error info:", errorInfo.componentStack);
        
        // Send to error tracking service
        logErrorToService(error, errorInfo);
      }
      
      render() {
        if (this.state.hasError) {
          return <h1>Something went wrong</h1>;
        }
        return this.props.children;
      }
    }
  • componentDidCatch is called after an error has been thrown.
  • Use it to log errors to services like Sentry or DataDog.
  • Include the error stack and component tree information in logs.
  • Never throw errors from componentDidCatch itself.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Error Boundaries
Chapter 12 · Page 87
Intermediate

React Error Boundaries

(continued)

What Error Boundaries Catch

  • Error boundaries catch errors during rendering in child components.
    // This error is caught
    function Child() {
      throw new Error("Oops");
    }
    
    <ErrorBoundary>
      <Child /> {/* Error is caught here */}
    </ErrorBoundary>
  • They do NOT catch errors from event handlers or async code.
    // This error is NOT caught
    <button onClick={() => {
      throw new Error("Oops");
    }}>
      Click me
    </button>
    
    // Use try-catch for these instead
    <button onClick={() => {
      try {
        riskyOperation();
      } catch (error) {
        // Handle error
      }
    }}>
      Safe click
    </button>
  • Error boundaries don't catch errors in the boundary itself.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Error Boundaries
Chapter 12 · Page 88
Intermediate

React Error Boundaries

(continued)

Nested Error Boundaries

  • Use multiple error boundaries to granularly handle errors.
    <ErrorBoundary>
      <Header />
      <ErrorBoundary>
        <MainContent />
      </ErrorBoundary>
      <Sidebar />
    </ErrorBoundary>
  • Errors in MainContent are caught by the inner boundary.
  • Errors in Header or Sidebar are caught by the outer boundary.
  • This lets you keep the rest of the app running when part fails.
  • Use granular boundaries for user-facing features like widgets.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Error Boundaries
Chapter 12 · Page 89
Intermediate

React Error Boundaries

(continued)

Error Boundary Patterns

  • Show a user-friendly error message with recovery option.
    class ErrorBoundary extends React.Component {
      render() {
        if (this.state.hasError) {
          return (
            <div>
              <h1>Oops, something went wrong</h1>
              <p>Please try refreshing the page</p>
              <button onClick={() => window.location.reload()}>
                Reload Page
              </button>
            </div>
          );
        }
        return this.props.children;
      }
    }
  • Create reusable error boundary components for common layouts.
    <PageErrorBoundary>
      <PageContent />
    </PageErrorBoundary>
  • Combine error boundaries with error tracking for production monitoring.
  • Reset error state when user navigates to a new page.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Error Boundaries
Chapter 12 · Page 90
Intermediate

React Error Boundaries

(FAQ)

FAQ

Define a class component that implements getDerivedStateFromError to update state when an error is caught, then return fallback UI in render when that state is set. You can also implement componentDidCatch to receive the error and info objects for logging.

Error boundaries only intercept errors thrown during the React render cycle — event handlers run outside that cycle, so you need a try-catch block inside the handler itself.

Yes, and it's the recommended approach — nest them so a broken widget or feature only unmounts its own subtree while the rest of the page stays functional. Placing one at the root acts as a last-resort safety net, not a replacement for granular boundaries.

Implement componentDidCatch(error, info) in your class component and call your logging service there — info.componentStack gives you the full component trace, which is invaluable for debugging production issues.

No — as of React 18, error boundaries must be class components because the required lifecycle methods (getDerivedStateFromError, componentDidCatch) have no hook equivalents. Libraries like react-error-boundary wrap this pattern into a reusable component so you can use it declaratively without writing the class yourself.

Useful Cheatsheetsusefulcheatsheets.com
React Hooks
Chapter 13 · Page 91
Intermediate

React Hooks

Quick reference for core and advanced React hooks with usage rules and patterns.

TL;DR

  1. 01useState and useEffect cover most common state and side-effect needs.
  2. 02Use useCallback and useMemo only when you have a proven performance issue.
  3. 03Only call hooks at the top level and inside React function components.

Tips

  1. 01Create custom hooks to share logic between components — extract state and effects into reusable hooks with names starting with "use".

Warnings

  1. 01Always follow the Rules of Hooks — calling hooks conditionally or from non-component functions breaks React's internal state management.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Hooks
Chapter 13 · Page 92
Intermediate

React Hooks

(continued)

Core Hooks

  • useState for managing local component state.
    const [count, setCount] = useState(0);
    return <button onClick={() => setCount(count + 1)}>{count}</button>;
  • useEffect for side effects and cleanup.
    useEffect(() => {
      fetch("/api/data").then(r => r.json()).then(setData);
      return () => { /* cleanup */ };
    }, []);
  • useContext for accessing shared data from a provider.
    const { theme } = useContext(ThemeContext);
    return <div style={{ background: theme }}>Content</div>;
  • useReducer for state with multiple related actions.
    const [state, dispatch] = useReducer(reducer, { count: 0 });
    return <button onClick={() => dispatch({ type: "INCREMENT" })}>
      {state.count}
    </button>;
  • useRef for accessing DOM nodes or storing mutable values.
    const inputRef = useRef(null);
    return (
      <>
        <input ref={inputRef} />
        <button onClick={() => inputRef.current.focus()}>Focus</button>
      </>
    );
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Hooks
Chapter 13 · Page 93
Intermediate

React Hooks

(continued)

Performance Hooks

  • useCallback memoizes a function reference across renders.
    const handleClick = useCallback(() => {
      doSomething(value);
    }, [value]);
  • useMemo memoizes a computed value across renders.
    const expensiveValue = useMemo(() => {
      return computeExpensiveValue(a, b);
    }, [a, b]);
  • Use useCallback to prevent child re-renders when passing callbacks.
    const handleSubmit = useCallback((data) => {
      saveData(data);
    }, []); // stable reference — MemoizedChild won't re-render
    
    return <MemoizedChild onSubmit={handleSubmit} />;
  • Use useMemo to avoid recalculating expensive derived data.
    const sortedItems = useMemo(
      () => [...items].sort((a, b) => a.name.localeCompare(b.name)),
      [items]
    );
  • Both hooks only optimize — they don't change behavior.
    // Add these only after profiling shows a real bottleneck
    // Premature optimization adds complexity without benefit
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Hooks
Chapter 13 · Page 94
Intermediate

React Hooks

(continued)

Ref Hooks

  • useRef creates a mutable ref that persists across renders.
    const inputRef = useRef(null);
    return (
      <>
        <input ref={inputRef} />
        <button onClick={() => inputRef.current.focus()}>Focus</button>
      </>
    );
  • useImperativeHandle exposes custom methods to parent components.
    useImperativeHandle(ref, () => ({
      focus: () => inputRef.current.focus(),
      clear: () => { inputRef.current.value = ""; }
    }));
  • Store mutable values that don't need to trigger re-renders.
    const timerRef = useRef(null);
    function start() { timerRef.current = setInterval(tick, 1000); }
    function stop() { clearInterval(timerRef.current); }
  • Track previous prop or state value for comparisons.
    const prevCountRef = useRef(count);
    useEffect(() => { prevCountRef.current = count; }, [count]);
    const prevCount = prevCountRef.current; // value from last render
  • Forward refs to child components with forwardRef.
    const Input = forwardRef((props, ref) => (
      <input ref={ref} {...props} />
    ));
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Hooks
Chapter 13 · Page 95
Intermediate

React Hooks

(continued)

Advanced Hooks

  • useLayoutEffect runs synchronously before paint — use for measurements.
    useLayoutEffect(() => {
      // Runs before browser paints — safe to read DOM measurements here
      const height = elementRef.current.offsetHeight;
      setHeight(height);
    }, []);
  • useDebugValue shows hook values in React DevTools.
    function useCustomHook(value) {
      useDebugValue(value > 10 ? "large" : "small");
      return value;
    }
  • useTransition marks an update as non-urgent to keep UI responsive.
    const [isPending, startTransition] = useTransition();
    startTransition(() => {
      setSearchResults(results); // won't block input from being typed
    });
  • useDeferredValue defers a value update without a transition.
    const deferredQuery = useDeferredValue(searchQuery);
    const results = useMemo(() => filterList(deferredQuery), [deferredQuery]);
  • useId generates a stable unique ID for accessibility attributes.
    function Input({ label }) {
      const id = useId();
      return (
        <>
          <label htmlFor={id}>{label}</label>
          <input id={id} />
        </>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Hooks
Chapter 13 · Page 96
Intermediate

React Hooks

(continued)

Hook Rules

  • Only call hooks at the top level, never in loops or conditions.
    // Good
    function Component() {
      const [count, setCount] = useState(0);
      useEffect(() => { /* ... */ }, []);
    }
    
    // Bad: hook inside condition
    if (condition) {
      const [count, setCount] = useState(0); // Wrong!
    }
  • Only call hooks from React components or custom hooks.
  • Use ESLint plugin to enforce rules automatically.
    {
      "plugins": ["react-hooks"],
      "rules": {
        "react-hooks/rules-of-hooks": "error",
        "react-hooks/exhaustive-deps": "warn"
      }
    }
  • Custom hooks must start with "use" so React enforces the rules.
    // Good: React treats useWindowWidth as a hook
    function useWindowWidth() {
      const [width, setWidth] = useState(window.innerWidth);
      useEffect(() => {
        const handler = () => setWidth(window.innerWidth);
        window.addEventListener("resize", handler);
        return () => window.removeEventListener("resize", handler);
      }, []);
      return width;
    }
  • Hooks must be called in the same order every render.
    // React tracks hooks by call order — conditional calls break this
    // Move conditions inside the hook body, not around it
    useEffect(() => {
      if (!isLoggedIn) return; // condition inside — safe
    }, [isLoggedIn]);
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Hooks
Chapter 13 · Page 97
Intermediate

React Hooks

(FAQ)

FAQ

Pass a dependency array as the second argument to useEffect — an empty array [] runs the effect once on mount, while listing specific values like [userId] re-runs only when those values change. Avoid placing the fetch URL or options object directly in the dependency array if they're recreated each render, as object identity changes will trigger the effect repeatedly.

useState triggers a re-render when updated, while useRef stores a mutable value in .current that persists across renders without causing re-renders. Use useRef for values you need to track (like timers, previous values, or DOM nodes) but don't want to drive the UI.

Use them only after profiling confirms a real performance problem — wrapping every function or computed value adds overhead and complexity that often outweighs any benefit. The most justified cases are passing stable callbacks to heavily optimized child components (React.memo) or skipping expensive recalculations in tight render loops.

React 18 Strict Mode intentionally double-invokes effects in development to surface bugs in effects that don't properly clean up. Return a cleanup function from your effect to handle unmounting correctly, and your production build will only run the effect once.

Extract the state and effects into a custom hook — a plain JavaScript function whose name starts with 'use' that calls other hooks internally. Each component that calls the custom hook gets its own isolated state, so sharing logic doesn't mean sharing state.

Useful Cheatsheetsusefulcheatsheets.com
React Lifecycle
Chapter 14 · Page 98
Intermediate

React Lifecycle

Understand React component lifecycle phases and map class methods to hooks.

TL;DR

  1. 01Every component goes through mount, update, and unmount phases.
  2. 02Class lifecycle methods map onto specific hooks and timing rules.
  3. 03React skips re-renders when memoization or keys say state is unchanged.

Tips

  1. 01Map each class lifecycle method to its hooks equivalent one at a time when migrating, rather than rewriting a whole component at once.
  2. 02Use React.memo or PureComponent to skip unnecessary re-renders instead of fighting them with manual shouldComponentUpdate logic.

Warnings

  1. 01Calling setState synchronously inside componentDidMount or its hook equivalent forces React to render twice before the browser paints anything.
  2. 02Updating state after a component unmounts logs a warning in class components and can cause stale closures in hooks-based equivalents.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Lifecycle
Chapter 14 · Page 99
Intermediate

React Lifecycle

(continued)

Component Lifecycle Phases

  • Mount: component is created and inserted into the DOM.
  • Update: component re-renders due to state or prop changes.
  • Unmount: component is removed from the DOM.
    function Component() {
      // Mount: run once
      useEffect(() => {
        console.log("Mounted");
      }, []);
      
      // Unmount: cleanup
      useEffect(() => {
        return () => console.log("Unmounting");
      }, []);
    }
  • React 18 Strict Mode mounts, unmounts, then remounts in development.
    // In development with Strict Mode, lifecycle methods run twice
    // This is intentional — it proves teardown and setup are symmetric
  • The same component instance updates without dismounting on re-renders.
    // Changing props or state updates the component
    // — it does NOT unmount and remount
  • A changed key prop forces React to unmount the old instance and mount a fresh one.
    // Changing the key remounts the component from scratch,
    // resetting all of its internal state
    <UserPanel key={userId} />
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Lifecycle
Chapter 14 · Page 100
Intermediate

React Lifecycle

(continued)

Class Lifecycle Methods

  • componentDidMount fires once after the component is first inserted into the DOM.
    class Profile extends React.Component {
      componentDidMount() {
        console.log("Mounted, safe to fetch or measure DOM");
      }
    }
  • componentDidUpdate fires after every re-render except the first, receiving previous props and state.
    componentDidUpdate(prevProps, prevState) {
      if (prevProps.userId !== this.props.userId) {
        this.loadUser(this.props.userId);
      }
    }
  • componentWillUnmount fires once, right before React removes the component from the DOM.
    componentWillUnmount() {
      this.subscription.unsubscribe();
    }
  • shouldComponentUpdate runs before re-rendering and can return false to skip the render entirely.
    shouldComponentUpdate(nextProps) {
      // Skip re-render if the displayed value hasn't changed
      return nextProps.value !== this.props.value;
    }
  • getDerivedStateFromProps runs before every render to sync state from incoming props.
    static getDerivedStateFromProps(props, state) {
      if (props.id !== state.prevId) {
        return { selected: null, prevId: props.id };
      }
      return null;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Lifecycle
Chapter 14 · Page 101
Intermediate

React Lifecycle

(continued)

Class to Hooks Mapping

  • componentDidMount maps to useEffect with an empty dependency array, since both run once after the initial render.
    // Class: componentDidMount() { ... }
    useEffect(() => {
      // runs once, after mount
    }, []);
  • componentDidUpdate maps to useEffect with specific dependencies, since both run after updates to those values.
    // Class: componentDidUpdate(prevProps) { if (prevProps.id !== this.props.id) ... }
    useEffect(() => {
      // runs after mount AND after every id change
    }, [id]);
  • componentWillUnmount maps to the function returned from useEffect.
    // Class: componentWillUnmount() { this.sub.unsubscribe(); }
    useEffect(() => {
      const sub = subscribe();
      return () => sub.unsubscribe();
    }, []);
  • shouldComponentUpdate maps to wrapping the function component in React.memo.
    // Class: extends React.PureComponent, or custom shouldComponentUpdate
    const Row = React.memo((props) => {
      return <tr>{props.label}</tr>;
    });
  • getDerivedStateFromProps usually maps to deriving the value directly during render instead of storing it in state.
    // Class: static getDerivedStateFromProps synced state.prevId to props.id
    const [prevId, setPrevId] = useState(id);
    if (id !== prevId) {
      setPrevId(id);
      setSelected(null); // adjust state during render, not in an effect
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Lifecycle
Chapter 14 · Page 102
Intermediate

React Lifecycle

(continued)

Reconciliation and Re-Renders

  • A parent re-rendering re-renders every child by default, even children whose props are unchanged.
    // Parent state change re-renders Child even if childProp never changes
    function Parent() {
      const [count, setCount] = useState(0);
      return <Child childProp="static" />;
    }
  • React.memo skips re-rendering a function component when its props are shallowly equal to the last render.
    const Child = React.memo(({ childProp }) => {
      return <div>{childProp}</div>;
    }); // re-renders only when childProp's reference changes
  • PureComponent is the class equivalent of React.memo, shallow-comparing props and state automatically.
    class Row extends React.PureComponent {
      // Automatically skips render if props/state are shallowly equal
      render() { return <tr>{this.props.label}</tr>; }
    }
  • Changing a list item's key forces React to discard the old DOM node and lifecycle state instead of updating it in place.
    // Using array index as key can cause React to reuse the wrong instance
    // when items are reordered — prefer a stable id
    items.map((item) => <Row key={item.id} item={item} />)
  • useMemo and useCallback stop new object or function references from defeating memoization on the next render.
    // Without useCallback, onSave is a new function every render,
    // which breaks React.memo on Child
    const onSave = useCallback(() => save(id), [id]);
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Lifecycle
Chapter 14 · Page 103
Intermediate

React Lifecycle

(continued)

Common Lifecycle Bugs

  • Calling setState synchronously inside componentDidMount triggers an extra render before the browser paints.
    componentDidMount() {
      // Causes a second render right after the first — avoid when possible
      this.setState({ ready: true });
    }
  • Updating state in componentWillUnmount or after an effect's cleanup has run logs a no-op warning.
    componentWillUnmount() {
      // Warning: Can't perform a React state update on an unmounted component
      this.setState({ closed: true }); // remove this call instead
    }
  • Child lifecycle methods fire before the parent's during mount, but the parent's fire first during unmount.
    // Mount order: Child.componentDidMount, then Parent.componentDidMount
    // Unmount order: Parent.componentWillUnmount, then Child.componentWillUnmount
  • Calling shouldComponentUpdate but forgetting to compare nested fields silently blocks needed re-renders.
    shouldComponentUpdate(nextProps) {
      // Bug: compares the array reference, not its contents
      return nextProps.items !== this.props.items;
    }
  • Mixing getDerivedStateFromProps with side effects like fetching breaks its contract — it must stay pure.
    static getDerivedStateFromProps(props) {
      // Wrong: side effects don't belong here, this method must be pure
      // fetch(props.url) — move data fetching to componentDidMount/useEffect instead
      return null;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Lifecycle
Chapter 14 · Page 104
Intermediate

React Lifecycle

(FAQ)

FAQ

componentDidMount is a class lifecycle method that fires once after the initial render, before the browser paints in some cases. useEffect with an empty dependency array is the closest functional equivalent, but it always runs after paint, and it folds in cleanup logic that previously required a separate componentWillUnmount method.

React re-renders a component whenever its parent re-renders, regardless of whether the props look the same, unless you opt out with React.memo or shouldComponentUpdate. Object and array props that are recreated on every parent render also count as changed, because React compares references, not deep equality.

React calls componentWillUnmount (or runs effect cleanup functions) synchronously before detaching the component from the DOM. This is the only guaranteed point to cancel timers, close connections, and unsubscribe — after this, the component instance is discarded and any further state updates are ignored or warned about.

Yes — wrap function components in React.memo or extend PureComponent for class components, both of which shallow-compare props and skip rendering when nothing changed. For state-driven re-renders inside the same component, shouldComponentUpdate or splitting state into smaller, more targeted pieces avoids unnecessary work.

React 18 Strict Mode intentionally mounts, unmounts, and remounts components in development to surface lifecycle code that isn't resilient to being torn down and set up again. This double-invoke only happens in development with Strict Mode enabled — production builds run each lifecycle step once.

Useful Cheatsheetsusefulcheatsheets.com
React Portals
Chapter 15 · Page 105
Intermediate

React Portals

Use portals to render components outside the DOM hierarchy for modals and overlays.

TL;DR

  1. 01Use ReactDOM.createPortal to render outside the DOM tree.
  2. 02Portals are useful for modals, tooltips, and dropdowns.
  3. 03Event bubbling still works through portals to parent components.

Tips

  1. 01Use portals for any UI that needs to visually escape its parent container without needing absolute positioning tricks.

Warnings

  1. 01Event bubbling in portals goes through the React tree, not the DOM tree, so document event listeners won't catch portal events from within React.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Portals
Chapter 15 · Page 106
Intermediate

React Portals

(continued)

Creating Portals

  • Use ReactDOM.createPortal to render a component at a different location.
    import { createPortal } from "react-dom";
    
    function Modal({ children }) {
      const root = document.getElementById("modal-root");
      return createPortal(
        <div className="modal">{children}</div>,
        root
      );
    }
  • Create a target element in your HTML for the portal.
    <div id="root"></div>
    <div id="modal-root"></div>
  • Portals render outside the component hierarchy but stay in React.
  • Perfect for modals, dropdowns, tooltips, and overlays.
  • Check that the target element exists before rendering.
    function Portal({ children, containerId = "portal-root" }) {
      const container = document.getElementById(containerId);
      if (!container) return null;
      return createPortal(children, container);
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Portals
Chapter 15 · Page 107
Intermediate

React Portals

(continued)

Modal Implementation

  • Build a reusable modal using portals for proper layering.
    function Modal({ isOpen, onClose, children, title }) {
      if (!isOpen) return null;
      
      return createPortal(
        <div className="modal-backdrop" onClick={onClose}>
          <div className="modal-content" onClick={e => e.stopPropagation()}>
            <h2>{title}</h2>
            {children}
            <button onClick={onClose}>Close</button>
          </div>
        </div>,
        document.getElementById("portal-root")
      );
    }
    
    function App() {
      const [open, setOpen] = useState(false);
      return (
        <>
          <button onClick={() => setOpen(true)}>Open Modal</button>
          <Modal isOpen={open} onClose={() => setOpen(false)} title="Hello">
            Modal content here
          </Modal>
        </>
      );
    }
  • Use stopPropagation to prevent backdrop clicks from closing the modal.
  • Trap keyboard focus inside the modal for accessibility compliance.
    useEffect(() => {
      if (!isOpen) return;
      const focusable = modalRef.current?.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );
      focusable?.[0]?.focus();
    }, [isOpen]);
  • Close the modal when the user presses the Escape key.
    useEffect(() => {
      function handleKey(e) {
        if (e.key === "Escape") onClose();
      }
      if (isOpen) document.addEventListener("keydown", handleKey);
      return () => document.removeEventListener("keydown", handleKey);
    }, [isOpen, onClose]);
  • Prevent body scroll while the modal is open.
    useEffect(() => {
      document.body.style.overflow = isOpen ? "hidden" : "";
      return () => { document.body.style.overflow = ""; };
    }, [isOpen]);
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Portals
Chapter 15 · Page 108
Intermediate

React Portals

(continued)

Event Bubbling Through Portals

  • Events bubble from portals to ancestors in the React component tree.
    function Parent() {
      const handleClick = (e) => {
        if (e.target.closest(".modal")) {
          console.log("Modal clicked");
        }
      };
      
      return (
        <div onClick={handleClick}>
          <Modal>Content</Modal>
        </div>
      );
    }
  • Event bubbling goes through the React tree, not the DOM tree.
  • This allows parent components to handle events from portaled children.
  • Useful for closing modals when clicking outside.
  • Use stopPropagation inside the portal to prevent bubbling to parent handlers.
    function Modal({ onClose, children }) {
      return createPortal(
        <div className="backdrop" onClick={onClose}>
          <div className="content" onClick={e => e.stopPropagation()}>
            {children}
          </div>
        </div>,
        document.getElementById("modal-root")
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Portals
Chapter 15 · Page 109
Intermediate

React Portals

(continued)

Common Portal Use Cases

  • Tooltips that overflow their containers and need body-level placement.
    function Tooltip({ content, children }) {
      return (
        <>
          {children}
          {createPortal(
            <div className="tooltip">{content}</div>,
            document.body
          )}
        </>
      );
    }
  • Dropdowns and autocomplete menus that escape overflow-hidden parents.
    function Dropdown({ isOpen, options }) {
      if (!isOpen) return null;
      
      return createPortal(
        <ul className="dropdown">
          {options.map(opt => <li key={opt}>{opt}</li>)}
        </ul>,
        document.body
      );
    }
  • Notifications and toasts floating above all other content.
    function Toast({ message }) {
      return createPortal(
        <div className="toast">{message}</div>,
        document.getElementById("toast-container")
      );
    }
  • Context menus that must appear at the cursor position on the page.
    function ContextMenu({ x, y, items, onClose }) {
      return createPortal(
        <ul className="context-menu" style={{ top: y, left: x }}>
          {items.map(item => (
            <li key={item.label} onClick={() => { item.action(); onClose(); }}>
              {item.label}
            </li>
          ))}
        </ul>,
        document.body
      );
    }
  • Lightboxes that render full-screen images above all page content.
    function Lightbox({ src, alt, onClose }) {
      return createPortal(
        <div className="lightbox" onClick={onClose}>
          <img src={src} alt={alt} />
        </div>,
        document.getElementById("portal-root")
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Portals
Chapter 15 · Page 110
Intermediate

React Portals

(continued)

Portal Best Practices

  • Always have a target element in your HTML.
    <body>
      <div id="root"></div>
      <div id="modal-root"></div>
      <div id="tooltip-root"></div>
    </body>
  • Clean up portals when components unmount.
    useEffect(() => {
      return () => {
        // Cleanup if needed
      };
    }, []);
  • Use z-index in CSS to layer portaled elements correctly.
    .modal-backdrop {
      z-index: 1000;
    }
  • Manage focus when opening a modal for keyboard accessibility.
    useEffect(() => {
      if (isOpen) {
        closeButtonRef.current?.focus();
      }
    }, [isOpen]);
  • Add role="dialog" and aria-modal="true" for screen reader support.
    createPortal(
      <div role="dialog" aria-modal="true" aria-labelledby="modal-title">
        <h2 id="modal-title">Confirm Delete</h2>
        {children}
      </div>,
      document.getElementById("modal-root")
    )
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Portals
Chapter 15 · Page 111
Intermediate

React Portals

(FAQ)

FAQ

Use ReactDOM.createPortal(element, document.body) to mount the modal directly on the body while keeping it inside your React tree. This means context providers and state from parent components remain accessible inside the portal.

Portal events bubble through the React component tree, not the DOM tree, so native document-level listeners won't fire for clicks inside a portal. Move event handling into React components using onClick or similar synthetic event handlers instead.

Portals are preferable when a parent component has overflow: hidden, transform, or z-index stacking context issues that would clip or obscure your overlay even with fixed positioning. Use position: fixed inside a portal for the cleanest combination of both.

Yes — portal children still belong to the React tree where createPortal is called, so Context, Redux state, and hooks all work normally even though the portal renders to a different DOM node.

createPortal keeps the component inside React's reconciliation cycle, giving you lifecycle methods, hooks, and automatic cleanup on unmount. Raw DOM manipulation bypasses React entirely, requiring manual cleanup and losing all React features.

Useful Cheatsheetsusefulcheatsheets.com
React Ref Patterns
Chapter 16 · Page 112
Intermediate

React Ref Patterns

Real-world React ref patterns: click-outside detection, ref arrays, previous value tracking, ResizeObserver, and TypeScript typing.

TL;DR

  1. 01Use ref.current.contains() to detect clicks outside a component.
  2. 02Track a previous value by assigning to a ref inside useEffect.
  3. 03Combine useRef with ResizeObserver for accurate element measurements.

Tips

  1. 01Wrap the callback prop in useCallback in the parent to keep the dependency array stable and avoid re-registering the document listener on every render.

Warnings

  1. 01On the very first render, usePrevious returns undefined because no previous value exists. Guard with prevValue !== undefined before using it in comparisons.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Ref Patterns
Chapter 16 · Page 113
Intermediate

React Ref Patterns

(continued)

Click-Outside Detection

The click-outside pattern is essential for dropdowns, modals, and tooltips. Attach a ref to the container, listen for mousedown on document, and call contains() to decide whether to close.

  • Full reusable hook:
    import { useRef, useEffect } from 'react';
    
    function useClickOutside(callback) {
      const ref = useRef(null);
    
      useEffect(() => {
        function handleMouseDown(e) {
          if (ref.current && !ref.current.contains(e.target)) {
            callback();
          }
        }
        document.addEventListener('mousedown', handleMouseDown);
        return () => document.removeEventListener('mousedown', handleMouseDown);
      }, [callback]);
    
      return ref;
    }
    
    function Dropdown() {
      const [open, setOpen] = useState(false);
      const dropdownRef = useClickOutside(() => setOpen(false));
    
      return (
        <div ref={dropdownRef}>
          <button onClick={() => setOpen(o => !o)}>Toggle</button>
          {open && <ul><li>Item 1</li><li>Item 2</li></ul>}
        </div>
      );
    }
  • Use mousedown rather than click so the close action fires before any click handler on the newly focused element. This prevents flickering when clicking between two dropdowns.
  • Guard with ref.current && before calling contains — the ref may be null if the component unmounted between renders.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Ref Patterns
Chapter 16 · Page 114
Intermediate

React Ref Patterns

(continued)

Ref Arrays for Dynamic Lists

When you render a list with map and need a ref to each item, you can't call useRef in a loop (that violates the Rules of Hooks). Instead, store an array inside a single ref and populate it with callback refs.

  • Hold all item refs in one ref object and assign each slot via a callback ref:
    import { useRef } from 'react';
    
    function VirtualList({ items }) {
      const itemRefs = useRef([]);
    
      function scrollToItem(index) {
        itemRefs.current[index]?.scrollIntoView({ behavior: 'smooth' });
      }
    
      return (
        <>
          <button onClick={() => scrollToItem(5)}>Jump to #5</button>
          <ul>
            {items.map((item, i) => (
              <li
                key={item.id}
                ref={node => { itemRefs.current[i] = node; }}
              >
                {item.label}
              </li>
            ))}
          </ul>
        </>
      );
    }
  • The inline callback node => { itemRefs.current[i] = node; } runs each render, keeping the array in sync when items are added or removed. When a node unmounts React calls the callback with null, so you may want to filter: itemRefs.current = itemRefs.current.filter(Boolean) before use.
  • This pattern also works with Map keyed by item ID for sparse or keyed access:
    const refMap = useRef(new Map());
    // ...
    ref={node => {
      if (node) refMap.current.set(item.id, node);
      else refMap.current.delete(item.id);
    }}
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Ref Patterns
Chapter 16 · Page 115
Intermediate

React Ref Patterns

(continued)

Previous Value Tracking

React has no built-in way to read a previous prop or state value, but a ref updated inside useEffect gives you exactly that — the value from the render that just committed.

  • Generic usePrevious hook:
    import { useRef, useEffect } from 'react';
    
    function usePrevious(value) {
      const ref = useRef(undefined);
      useEffect(() => {
        ref.current = value; // runs AFTER render, so ref holds previous value during render
      }, [value]);
      return ref.current;
    }
    
    function PriceDisplay({ price }) {
      const prevPrice = usePrevious(price);
      const direction = price > prevPrice ? '↑' : price < prevPrice ? '↓' : '';
    
      return (
        <p>
          {price} {direction} (was {prevPrice ?? 'N/A'})
        </p>
      );
    }
  • How the timing works: during render, ref.current still holds the previous value because the effect hasn't fired yet. After React commits the DOM, the effect updates ref.current to the new value, ready for the next render.
  • Use this to skip an effect on the initial render by comparing current and previous values, or to animate differences between frames.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Ref Patterns
Chapter 16 · Page 116
Intermediate

React Ref Patterns

(continued)

Measuring with ResizeObserver

ResizeObserver fires whenever a watched element's content box changes size. Combining it with useRef gives you live width and height measurements without polling or window resize events.

  • Full hook that returns live dimensions:
    import { useRef, useState, useEffect } from 'react';
    
    function useElementSize() {
      const ref = useRef(null);
      const [size, setSize] = useState({ width: 0, height: 0 });
    
      useEffect(() => {
        const node = ref.current;
        if (!node) return;
    
        const observer = new ResizeObserver(([entry]) => {
          const { width, height } = entry.contentRect;
          setSize({ width: Math.round(width), height: Math.round(height) });
        });
    
        observer.observe(node);
        return () => observer.disconnect(); // always clean up
      }, []);
    
      return [ref, size];
    }
    
    function ResponsiveCard() {
      const [cardRef, { width, height }] = useElementSize();
    
      return (
        <div ref={cardRef} style={{ resize: 'both', overflow: 'auto', padding: 16 }}>
          <p>{width} × {height}px — drag the corner to resize</p>
        </div>
      );
    }
  • ResizeObserver is available in all modern browsers (Chrome 64+, Firefox 69+, Safari 13.1+). For older environments, polyfill with resize-observer-polyfill.
  • The observer fires once immediately after observe() is called, so size is populated on mount without an extra effect or state initialization.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Ref Patterns
Chapter 16 · Page 117
Intermediate

React Ref Patterns

(continued)

TypeScript: Typing Refs

TypeScript has two ref types. Choosing the right one avoids unsafe non-null assertions and keeps autocompletion accurate.

TypecurrentCreated byUse case
RefObject<T>T | null (readonly)useRef<T>(null)DOM nodes React controls
MutableRefObject<T>T (writable)useRef<T>(initialValue)Timer IDs, counters, previous values
  • DOM element ref — always start with null:
    const inputRef = useRef<HTMLInputElement>(null);
    // inputRef is RefObject<HTMLInputElement>
    // Safe access with optional chaining:
    inputRef.current?.focus();
  • Mutable value ref — supply a real initial value so TypeScript infers MutableRefObject:
    const timerId = useRef<ReturnType<typeof setTimeout> | null>(null);
    // Assign later:
    timerId.current = setTimeout(fn, 500);
    clearTimeout(timerId.current!);
  • Typing a component that accepts a ref (React 19 style):
    import { useRef, useImperativeHandle } from 'react';
    
    type InputHandle = {
      focus: () => void;
      clear: () => void;
    };
    
    type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
      ref?: React.Ref<InputHandle>;
    };
    
    function FancyInput({ ref, ...props }: InputProps) {
      const innerRef = useRef<HTMLInputElement>(null);
    
      useImperativeHandle(ref, () => ({
        focus: () => innerRef.current?.focus(),
        clear: () => { if (innerRef.current) innerRef.current.value = ''; },
      }));
    
      return <input ref={innerRef} {...props} />;
    }
    
    // Parent:
    const formRef = useRef<InputHandle>(null);
    <FancyInput ref={formRef} placeholder="Email" />
  • Avoid using as React.MutableRefObject<T> to force-cast DOM refs — this bypasses null safety. Instead, use optional chaining or an early-return null check.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Ref Patterns
Chapter 16 · Page 118
Intermediate

React Ref Patterns

(FAQ)

FAQ

Attach a ref to the dropdown container, then add a mousedown listener on document inside a useEffect. In the handler, call ref.current.contains(e.target) — if it returns false, the click was outside and you can close the dropdown. Remove the listener in the cleanup function returned from useEffect.

Initialise a ref with useRef([]) to hold the array of DOM nodes, then pass each item a callback ref function that assigns node into the array at the correct index: ref={node => { listRef.current[i] = node; }}. This approach works even when the list length changes between renders.

Create a ref with useRef(), then run useEffect(() => { prevRef.current = value; }, [value]) after each render. Because effects run after the render is committed, prevRef.current always holds the value from the previous render cycle when you read it during the current render.

Use ResizeObserver when you need continuous size updates as the element grows or shrinks — for example, when the user resizes the window or when dynamic content changes the element's dimensions. getBoundingClientRect is a one-shot read and won't notify you of future changes. ResizeObserver fires a callback each time the observed element's size changes.

RefObject has a readonly current property typed as T | null — it models a DOM ref that React controls. MutableRefObject has a mutable current typed as T — it models a ref you update yourself, like a timer ID or previous value store. useRef(null) returns RefObject when given a type argument like useRef(null), while useRef(initialValue) with a non-null value returns MutableRefObject.

Useful Cheatsheetsusefulcheatsheets.com
React State Management
Chapter 17 · Page 119
Intermediate

React State Management

Manage application state with useState, useReducer, Context, Redux, or Zustand.

TL;DR

  1. 01Colocate state as close as possible to where it is used to avoid unnecessary re-renders.
  2. 02Use React Query or SWR for server state; reserve useState and Zustand for client-only state.
  3. 03Fix Redux performance with configureStore from Redux Toolkit — createStore is deprecated.

Tips

  1. 01Most apps have far less global state than developers think — UI state like hover, focus, and toggles almost always belongs locally, not in a shared store.
  2. 02Use typed hooks (useAppSelector, useAppDispatch) rather than the raw hooks so you get full TypeScript inference without casting at every call site.
  3. 03Start with useState and useContext, only add Redux or Zustand when state becomes too complex to manage.

Warnings

  1. 01Don't over-engineer state management — use the simplest solution that works for your app size.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React State Management
Chapter 17 · Page 120
Intermediate

React State Management

(continued)

Server State vs Client State

  • Server state is data that lives on the server and needs to stay in sync — fetch it with React Query or SWR, not useState.

    import { useQuery } from "@tanstack/react-query";
    
    function UserProfile({ id }: { id: string }) {
      const { data, isLoading, error } = useQuery({
        queryKey: ["user", id],
        queryFn: () => fetch(`/api/users/${id}`).then(r => r.json()),
        staleTime: 60_000 // cache for 1 minute
      });
      if (isLoading) return <p>Loading...</p>;
      if (error) return <p>Error loading user.</p>;
      return <h1>{data.name}</h1>;
    }
    
  • Client state is UI-only data that doesn't need to persist on the server — modal open/closed, selected tab, input value.

    // Client state: lives in the component, no server sync needed
    const [isMenuOpen, setIsMenuOpen] = useState(false);
    const [activeTab, setActiveTab] = useState("overview");
    
  • Use SWR for lightweight data fetching with automatic revalidation on focus and reconnect.

    import useSWR from "swr";
    const fetcher = (url: string) => fetch(url).then(r => r.json());
    
    function Dashboard() {
      const { data } = useSWR("/api/stats", fetcher, { refreshInterval: 30000 });
      return <p>Users: {data?.count}</p>;
    }
    
  • Avoid duplicating server state in useState — let the fetching library own it and read from its cache.

    // Wrong: duplicating server data into local state
    const [user, setUser] = useState(null);
    useEffect(() => { fetch("/api/me").then(r => r.json()).then(setUser); }, []);
    
    // Right: let React Query own the state
    const { data: user } = useQuery({ queryKey: ["me"], queryFn: () => fetch("/api/me").then(r => r.json()) });
    
  • Invalidate cache entries after mutations so the UI stays in sync without manual state updates.

    const queryClient = useQueryClient();
    await updateUser(data);
    queryClient.invalidateQueries({ queryKey: ["user", id] }); // refetches automatically
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React State Management
Chapter 17 · Page 121
Intermediate

React State Management

(continued)

State Colocation

  • Place state as close as possible to where it is used — don't lift state higher than necessary.

    // Wrong: lifting filter state to the top-level App when only ProductList needs it
    function App() {
      const [filter, setFilter] = useState("all");
      return <ProductList filter={filter} onFilterChange={setFilter} />;
    }
    
    // Right: colocate filter state inside ProductList where it belongs
    function ProductList() {
      const [filter, setFilter] = useState("all");
      // ...no prop drilling needed
    }
    
  • State used only in one component belongs in that component, not in a parent or global store.

  • State used by two sibling components should live in their closest common ancestor (lift state up one level, not all the way up).

    // Both SearchBox and SearchResults need the query — lift to their parent
    function SearchPage() {
      const [query, setQuery] = useState("");
      return (
        <>
          <SearchBox value={query} onChange={setQuery} />
          <SearchResults query={query} />
        </>
      );
    }
    
  • State used across unrelated parts of the app belongs in a global store (Zustand, Context, or Redux).

  • Avoid premature globalization — start local and lift only when a second consumer appears.

    // Progression: local → lifted → global
    // 1. useState in component (local)
    // 2. useState in parent (lifted) — when a sibling needs it
    // 3. Zustand or Context (global) — when distant components need it
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React State Management
Chapter 17 · Page 122
Intermediate

React State Management

(continued)

State Performance Patterns

  • Use functional state updates when new state depends on previous state to avoid stale closures.

    // Wrong: may read stale count if React batches updates
    setCount(count + 1);
    
    // Right: always uses the latest committed state
    setCount(prev => prev + 1);
    setItems(prev => [...prev, newItem]);
    
  • Batch related state updates with Object.assign or a single state object to avoid extra renders.

    // Two setState calls = two renders
    setLoading(true);
    setError(null);
    
    // One setState call = one render
    setState(prev => ({ ...prev, loading: true, error: null }));
    
  • Use Zustand's selector to subscribe only to the slice of state a component needs.

    const useStore = create((set) => ({
      count: 0, user: null, theme: "light",
      setCount: (n) => set({ count: n }),
    }));
    
    // Only re-renders when count changes, not when user or theme changes
    const count = useStore(state => state.count);
    
  • Derive values during render instead of storing derived state — computed values don't need setState.

    // Wrong: storing derived state
    const [items, setItems] = useState([]);
    const [count, setCount] = useState(0); // always equals items.length
    
    // Right: derive during render
    const [items, setItems] = useState([]);
    const count = items.length; // computed, always accurate, no sync needed
    
  • Use React.memo plus stable callback references to prevent child re-renders from parent state changes.

    const handleClick = useCallback(() => dispatch({ type: "SUBMIT" }), [dispatch]);
    const ExpensiveChild = React.memo(({ onClick }) => <button onClick={onClick}>Submit</button>);
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React State Management
Chapter 17 · Page 123
Intermediate

React State Management

(continued)

Redux for Large Apps

  • Use Redux Toolkit's configureStorecreateStore is deprecated since Redux 4.x.

    import { configureStore } from "@reduxjs/toolkit";
    import counterReducer from "./counterSlice";
    
    export const store = configureStore({
      reducer: { counter: counterReducer }
    });
    export type RootState = ReturnType<typeof store.getState>;
    export type AppDispatch = typeof store.dispatch;
    
  • Define slices with createSlice to eliminate action type constants and manual spread reducers.

    import { createSlice } from "@reduxjs/toolkit";
    
    const counterSlice = createSlice({
      name: "counter",
      initialState: { value: 0 },
      reducers: {
        increment: state => { state.value += 1; }, // Immer allows direct mutation
        decrement: state => { state.value -= 1; },
        setValue: (state, action) => { state.value = action.payload; }
      }
    });
    export const { increment, decrement, setValue } = counterSlice.actions;
    export default counterSlice.reducer;
    
  • Wrap your app in <Provider store={store}> to make state available to all components.

    import { Provider } from "react-redux";
    root.render(<Provider store={store}><App /></Provider>);
    
  • Read state with useSelector and dispatch actions with useDispatch.

    function Counter() {
      const count = useSelector((state: RootState) => state.counter.value);
      const dispatch = useDispatch<AppDispatch>();
      return <button onClick={() => dispatch(increment())}>{count}</button>;
    }
    
  • Use Redux DevTools to inspect state history, replay actions, and debug time-travel in development.

Notes
Useful Cheatsheetsusefulcheatsheets.com
React State Management
Chapter 17 · Page 124
Intermediate

React State Management

(continued)

Lightweight Alternatives

  • Create a Zustand store with state and actions in a single create call.

    import { create } from "zustand";
    
    const useStore = create<{ count: number; increment: () => void }>((set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
      decrement: () => set((state) => ({ count: state.count - 1 })),
    }));
    
    function Counter() {
      const { count, increment } = useStore();
      return <button onClick={increment}>Count: {count}</button>;
    }
    
  • Use Jotai atoms for fine-grained, component-level shared state without a central store.

    import { atom, useAtom } from "jotai";
    
    const countAtom = atom(0);
    
    function Counter() {
      const [count, setCount] = useAtom(countAtom);
      return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
    }
    
  • Use Recoil atoms and selectors to share state and compute derived values declaratively.

    import { atom, selector } from "recoil";
    
    const textState = atom({ key: "textState", default: "" });
    const charCountState = selector({
      key: "charCountState",
      get: ({ get }) => get(textState).length, // derived from textState
    });
    
  • Access Zustand state anywhere without wrapping the app in a Provider.

    // No <Provider> needed — call the hook directly in any component
    function Navbar() {
      const user = useStore((state) => state.user); // selector for one slice
      return <p>Hello {user?.name}</p>;
    }
    
  • Choose Zustand or Jotai over Redux when you want minimal boilerplate for small to medium apps.

    // Redux: actions + reducer + selectors + Provider setup
    // Zustand: one create() call, one hook, no Provider required
    // Jotai: one atom() call, one useAtom() hook, no Provider required
    const theme = useStore((s) => s.theme); // direct selector access
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React State Management
Chapter 17 · Page 125
Intermediate

React State Management

(FAQ)

FAQ

Use useReducer when you have multiple related state values that change together, or when next state depends on the previous state in complex ways. If you find yourself writing several useState calls that always update together, that's a signal to consolidate with useReducer.

Context API replaces Redux for many medium-sized apps, but it's not optimized for high-frequency updates — every consumer re-renders when context value changes. For apps with frequent global state updates or large teams needing strict state patterns, Redux or Zustand will perform better.

Zustand has a much smaller API surface and requires no boilerplate — you define a store in a single function call versus Redux's actions, reducers, and selectors. Choose Zustand for smaller teams or projects where Redux's structure feels like overhead.

Any state change in the context value causes all consumers to re-render, even if they only use a part of the value. Fix this by splitting large contexts into smaller focused ones, or memoizing the context value with useMemo.

Yes, and it's often the right call — local UI state in useState, shared server data in React Query or SWR, and global client state in Context or Zustand can coexist cleanly. Mixing solutions by concern keeps each layer simple rather than forcing everything through one system.

Useful Cheatsheetsusefulcheatsheets.com
React Suspense
Chapter 18 · Page 126
Intermediate

React Suspense

Load components and data with Suspense boundaries for better UX and streaming.

TL;DR

  1. 01Use Suspense to show loading states while components or data load.
  2. 02Wrap lazy-loaded components in Suspense for fallback UI.
  3. 03Combine with error boundaries for complete async error handling.

Tips

  1. 01Nest Suspense boundaries at different levels to show partial content incrementally while the rest loads.

Warnings

  1. 01Suspense for data fetching is still experimental in React 19 — check version compatibility and use established libraries like React Query for production apps.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Suspense
Chapter 18 · Page 127
Intermediate

React Suspense

(continued)

Suspense Basics

  • Wrap Suspense around components that may suspend.
    import { Suspense, lazy } from "react";
    
    const HeavyComponent = lazy(() => import("./HeavyComponent"));
    
    export default function App() {
      return (
        <Suspense fallback={<p>Loading...</p>}>
          <HeavyComponent />
        </Suspense>
      );
    }
  • The fallback prop shows while the component is loading.
  • Once loaded, the component replaces the fallback.
  • Suspense can wrap multiple components at once.
    <Suspense fallback={<div>Loading page...</div>}>
      <Header />
      <MainContent />
      <Sidebar />
    </Suspense>
  • Use a skeleton or spinner as the fallback for a better UX.
    <Suspense fallback={<PageSkeleton />}>
      <Dashboard />
    </Suspense>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Suspense
Chapter 18 · Page 128
Intermediate

React Suspense

(continued)

Code Splitting with Suspense

  • Use lazy to split code and load components on demand.
    const Dashboard = lazy(() => import("./pages/Dashboard"));
    const Settings = lazy(() => import("./pages/Settings"));
    
    export default function App({ page }) {
      return (
        <Suspense fallback={<p>Loading page...</p>}>
          {page === "dashboard" && <Dashboard />}
          {page === "settings" && <Settings />}
        </Suspense>
      );
    }
  • Each lazy component is a separate code chunk.
  • Chunks load only when the component is about to render.
  • Significantly reduces initial bundle size for large apps.
  • Combine with React Router for route-based code splitting.
    const Home = lazy(() => import("./pages/Home"));
    const Profile = lazy(() => import("./pages/Profile"));
    
    <Suspense fallback={<p>Loading...</p>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Suspense
Chapter 18 · Page 129
Intermediate

React Suspense

(continued)

Data Fetching with Suspense

  • Use React Query or SWR with Suspense for data fetching.
    function UserProfile({ userId }) {
      const { data: user } = useSuspenseQuery({
        queryKey: ["user", userId],
        queryFn: () => fetchUser(userId)
      });
      
      return <div>{user.name}</div>;
    }
    
    <Suspense fallback={<p>Loading user...</p>}>
      <UserProfile userId={1} />
    </Suspense>
  • Suspense handles loading states automatically.
  • No need for manual loading state management.
  • Error boundaries catch fetch errors thrown outside Suspense.
  • Wrap the data-fetching component, not the calling component.
    // UserProfile suspends internally — wrap it here
    function Page() {
      return (
        <Suspense fallback={<Spinner />}>
          <UserProfile userId={userId} />
        </Suspense>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Suspense
Chapter 18 · Page 130
Intermediate

React Suspense

(continued)

Nested Suspense Boundaries

  • Use multiple Suspense boundaries for granular control.
    <Suspense fallback={<p>Loading page...</p>}>
      <Header />
      <Suspense fallback={<p>Loading content...</p>}>
        <MainContent />
      </Suspense>
      <Suspense fallback={<p>Loading sidebar...</p>}>
        <Sidebar />
      </Suspense>
    </Suspense>
  • Each boundary can have its own fallback UI.
  • Inner boundaries resolve independently of each other.
  • Outer fallback shows only for outer-level suspensions.
  • Great for showing partial content while loading.
  • Place boundaries close to each suspending component for best UX.
    // Narrow boundary: only hides the part that's loading
    function ProductList() {
      return (
        <ul>
          {productIds.map(id => (
            <Suspense key={id} fallback={<li>Loading...</li>}>
              <ProductItem id={id} />
            </Suspense>
          ))}
        </ul>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Suspense
Chapter 18 · Page 131
Intermediate

React Suspense

(continued)

Advanced Patterns

  • Combine Suspense with Error Boundaries.
    <ErrorBoundary fallback={<p>Error loading page</p>}>
      <Suspense fallback={<p>Loading...</p>}>
        <PageContent />
      </Suspense>
    </ErrorBoundary>
  • Transition to new content with useTransition.
    function App() {
      const [isPending, startTransition] = useTransition();
      const [page, setPage] = useState("home");
      
      const navigate = (newPage) => {
        startTransition(() => setPage(newPage));
      };
      
      return (
        <Suspense fallback={<p>Loading...</p>}>
          {isPending && <p>Loading new page...</p>}
          <PageContent page={page} />
        </Suspense>
      );
    }
  • Use startTransition to keep the old UI visible while new content loads.
  • Preload lazy components early to avoid loading delays.
    // Preload on hover before user clicks
    const LazyPage = lazy(() => import("./Page"));
    
    function NavLink({ href, children }) {
      return (
        <a href={href} onMouseEnter={() => import("./Page")}>
          {children}
        </a>
      );
    }
  • Use the react-error-boundary package for ready-made Error Boundaries.
    import { ErrorBoundary } from "react-error-boundary";
    
    <ErrorBoundary fallbackRender={({ error }) => <p>Error: {error.message}</p>}>
      <Suspense fallback={<Spinner />}>
        <AsyncComponent />
      </Suspense>
    </ErrorBoundary>
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Suspense
Chapter 18 · Page 132
Intermediate

React Suspense

(FAQ)

FAQ

Suspense for data fetching is experimental in React 19 and not recommended for production without a supporting library. Use React Query or SWR instead — they integrate with Suspense via their own stable APIs and handle caching, retries, and deduplication.

Wrap React.lazy(() => import('./MyComponent')) in a Suspense boundary with a fallback prop: <Suspense fallback={}>. The fallback renders until the dynamic import resolves, so keep it lightweight to avoid layout shift.

All siblings suspend together — the single fallback shows until every component in the boundary is ready. To show components progressively as they load, wrap each in its own Suspense boundary so resolved components render immediately without waiting for slower siblings.

Suspense handles the loading state but not errors — a thrown promise triggers the fallback, while a thrown error propagates up to the nearest error boundary. Always pair Suspense with an ErrorBoundary component wrapping it to catch fetch failures or import errors.

Yes — Next.js App Router uses React's streaming SSR with Suspense, sending HTML in chunks as each boundary resolves on the server. Wrap async Server Components in Suspense to stream partial content to the client rather than blocking the entire page render.

Useful Cheatsheetsusefulcheatsheets.com
React Testing
Chapter 19 · Page 133
Intermediate

React Testing

Test React components with Jest and React Testing Library to verify user behavior.

TL;DR

  1. 01Use React Testing Library to render components and query the DOM.
  2. 02Simulate user actions with userEvent, not the lower-level fireEvent.
  3. 03Query by role and visible text, not by class names or test IDs.

Tips

  1. 01Test user behavior, not implementation — query by role or visible text, not by test IDs.

Warnings

  1. 01Avoid testing implementation details like internal state — test what users see and interact with.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing
Chapter 19 · Page 134
Intermediate

React Testing

(continued)

Basic Test Setup

  • Create a test file next to the component.
    // Button.test.js
    import { render, screen } from '@testing-library/react';
    import Button from './Button';
    
    test('renders a button', () => {
      render(<Button label="Click me" />);
      expect(screen.getByRole('button')).toBeInTheDocument();
    });
  • Run tests with the npm test command.
    npm test              # watch mode
    npm test -- --ci      # run once for CI
    npm test -- --coverage # generate coverage report
  • Install the necessary packages for a new project.
    npm install --save-dev @testing-library/react @testing-library/user-event @testing-library/jest-dom
  • Import jest-dom matchers to enable toBeInTheDocument and similar.
    // setupTests.js — imported in jest config
    import '@testing-library/jest-dom';
  • Use describe to group related tests.
    describe("Button", () => {
      test("renders the label", () => { /* ... */ });
      test("calls onClick when pressed", () => { /* ... */ });
      test("is disabled when prop is set", () => { /* ... */ });
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing
Chapter 19 · Page 135
Intermediate

React Testing

(continued)

Rendering Components

  • Render a component and query the result with screen.
    import { render, screen } from '@testing-library/react';
    
    test('renders component', () => {
      render(<MyComponent />);
      expect(screen.getByText('Expected text')).toBeInTheDocument();
    });
  • Use semantic queries to find elements the way users see them.
    screen.getByRole('button', { name: 'Submit' });
    screen.getByText('Label');
    screen.getByPlaceholderText('Enter name');
    screen.getByLabelText('Email');
  • Use getBy for elements that must exist, queryBy for optional ones.
    screen.getByRole('button');         // throws if missing
    screen.queryByText('Error');        // returns null if missing
    await screen.findByText('Loaded');  // waits for element to appear
  • Wrap providers around components that need context.
    test('shows user name from context', () => {
      render(
        <UserContext.Provider value={{ user: { name: 'Alice' } }}>
          <Greeting />
        </UserContext.Provider>
      );
      expect(screen.getByText('Hello, Alice')).toBeInTheDocument();
    });
  • Use rerender to test how a component responds to prop changes.
    const { rerender } = render(<Badge count={0} />);
    expect(screen.getByText('0')).toBeInTheDocument();
    
    rerender(<Badge count={5} />);
    expect(screen.getByText('5')).toBeInTheDocument();
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing
Chapter 19 · Page 136
Intermediate

React Testing

(continued)

User Interactions

  • Simulate user clicks with userEvent.click.
    import userEvent from '@testing-library/user-event';
    
    test('handles click', async () => {
      const user = userEvent.setup();
      render(<Counter />);
      
      const button = screen.getByRole('button', { name: 'Increment' });
      await user.click(button);
      
      expect(screen.getByText('Count: 1')).toBeInTheDocument();
    });
  • Type into inputs with userEvent.type.
    test('updates input on type', async () => {
      const user = userEvent.setup();
      render(<SearchBox />);
      
      const input = screen.getByPlaceholderText('Search...');
      await user.type(input, 'React hooks');
      
      expect(input).toHaveValue('React hooks');
    });
  • Submit a form to test validation and submission handlers.
    test('submits the form', async () => {
      const user = userEvent.setup();
      const onSubmit = jest.fn();
      render(<LoginForm onSubmit={onSubmit} />);
      
      await user.type(screen.getByLabelText('Email'), 'alice@example.com');
      await user.type(screen.getByLabelText('Password'), 'secret123');
      await user.click(screen.getByRole('button', { name: 'Log in' }));
      
      expect(onSubmit).toHaveBeenCalledWith({ email: 'alice@example.com' });
    });
  • Check a checkbox or select from a dropdown.
    await user.click(screen.getByRole('checkbox', { name: 'Accept terms' }));
    expect(screen.getByRole('checkbox')).toBeChecked();
    
    await user.selectOptions(screen.getByRole('combobox'), 'Option B');
  • Use keyboard navigation to test accessibility interactions.
    await user.keyboard('{Tab}');                // move focus
    await user.keyboard('{Enter}');              // activate focused element
    await user.keyboard('{Escape}');             // close modal
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing
Chapter 19 · Page 137
Intermediate

React Testing

(continued)

Async Testing

  • Wait for elements to appear with waitFor.
    import { waitFor } from '@testing-library/react';
    
    test('loads data', async () => {
      render(<DataComponent />);
      
      await waitFor(() => {
        expect(screen.getByText('Data loaded')).toBeInTheDocument();
      });
    });
  • Use findBy queries as a shorter alternative to waitFor.
    // findBy automatically waits — no waitFor needed
    const item = await screen.findByText('Loaded item');
    expect(item).toBeInTheDocument();
  • Mock fetch to control async responses in tests.
    global.fetch = jest.fn(() =>
      Promise.resolve({
        json: () => Promise.resolve([{ id: 1, name: "Alice" }])
      })
    );
    
    test('renders fetched data', async () => {
      render(<UserList />);
      expect(await screen.findByText('Alice')).toBeInTheDocument();
    });
  • Test loading and error states for async components.
    test('shows loading then data', async () => {
      render(<DataComponent />);
      expect(screen.getByText('Loading...')).toBeInTheDocument();
      expect(await screen.findByText('Alice')).toBeInTheDocument();
    });
  • Use MSW (Mock Service Worker) for realistic API mocking.
    import { http, HttpResponse } from 'msw';
    import { server } from './mocks/server';
    
    test('handles API error', async () => {
      server.use(http.get('/api/users', () => HttpResponse.error()));
      render(<UserList />);
      expect(await screen.findByText('Failed to load')).toBeInTheDocument();
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing
Chapter 19 · Page 138
Intermediate

React Testing

(continued)

Mocking

  • Mock a function with jest.fn to track calls and set return values.
    const mockOnClick = jest.fn();
    render(<Button onClick={mockOnClick}>Click</Button>);
    await userEvent.setup().click(screen.getByRole('button'));
    
    expect(mockOnClick).toHaveBeenCalledTimes(1);
  • Mock an entire module with jest.mock.
    jest.mock('./api', () => ({
      fetchUser: jest.fn(() => Promise.resolve({ name: 'Alice' }))
    }));
  • Spy on a module method without replacing it entirely.
    import * as api from './api';
    jest.spyOn(api, 'fetchUser').mockResolvedValue({ name: 'Bob' });
  • Reset mocks between tests to avoid leaking state.
    afterEach(() => {
      jest.clearAllMocks(); // resets call counts and instances
    });
  • Mock timers to test debounced or delayed behavior.
    jest.useFakeTimers();
    render(<Debounced />);
    await userEvent.setup().type(input, 'hello');
    jest.advanceTimersByTime(300); // skip the debounce delay
    expect(screen.getByText('hello')).toBeInTheDocument();
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing
Chapter 19 · Page 139
Intermediate

React Testing

(FAQ)

FAQ

userEvent simulates real browser interactions more accurately — it triggers the full sequence of events (pointerdown, focus, input, click, etc.) that a real user would cause, while fireEvent dispatches a single synthetic event. Prefer userEvent for most interaction tests.

Use findBy queries (e.g., findByRole, findByText) which return a promise and automatically wait for the element to appear. Alternatively, wrap your assertion in waitFor(() => expect(...)) for more complex async scenarios.

Mock the fetch or axios module using jest.mock, or intercept requests with a library like msw (Mock Service Worker). Return controlled responses so your test verifies how the component handles success and error states without making real network calls.

Querying by test ID only works in tests and tells you nothing about what users actually experience. Prefer getByRole or getByText, which validate accessibility and visible content — they catch regressions that matter to users, not just DOM structure.

Pass a jest.fn() as the onSubmit prop, use userEvent.type to fill in fields, then userEvent.click the submit button. Assert with expect(mockHandler).toHaveBeenCalledWith(expect.objectContaining({ fieldName: 'value' })).

Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 140
Intermediate

React Testing Best Practices

Test React components effectively using React Testing Library, Vitest, and common testing patterns.

TL;DR

  1. 01Test user behavior, not implementation details.
  2. 02Use React Testing Library to query elements as users see them.
  3. 03Mock external dependencies but test component logic thoroughly.

Tips

  1. 01Test user behavior and critical paths thoroughly, even if it means longer tests — catching real bugs is more important than test speed.

Warnings

  1. 01Avoid testing implementation details like component state or internal functions, since refactoring will break tests that depend on the old structure.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 141
Intermediate

React Testing Best Practices

(continued)

Testing User Behavior

  • Test what users see and do, not how components are built.
    import { render, screen } from "@testing-library/react";
    import Button from "./Button";
    
    it("shows clicked message when button is clicked", async () => {
      render(<Button />);
      const button = screen.getByRole("button", { name: /click me/i });
      await userEvent.click(button);
      expect(screen.getByText("You clicked!")).toBeInTheDocument();
    });
  • Query elements like users would: by text, role, label, not by className.
    // Good: user sees this text
    screen.getByText("Welcome");
    screen.getByRole("button", { name: /submit/i });
    screen.getByLabelText("Email");
    
    // Bad: implementation detail
    screen.getByTestId("submit-btn");
  • Avoid testing component state or internal functions.
  • Focus on inputs, outputs, and user interactions.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 142
Intermediate

React Testing Best Practices

(continued)

Setup and Utilities

  • Configure Vitest with React Testing Library setup.
    // vitest.config.js
    import { defineConfig } from "vitest/config";
    import react from "@vitejs/plugin-react";
    
    export default defineConfig({
      plugins: [react()],
      test: {
        globals: true,
        environment: "jsdom"
      }
    });
  • Create a custom render function to set up providers.
    import { render } from "@testing-library/react";
    
    export function renderWithProviders(ui) {
      return render(<ThemeProvider>{ui}</ThemeProvider>);
    }
  • Use this custom render in all tests to reduce boilerplate.
    it("applies theme colors", () => {
      renderWithProviders(<Component />);
      // Component now has theme context
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 143
Intermediate

React Testing Best Practices

(continued)

Mocking Dependencies

  • Mock external API calls to keep tests fast and isolated.
    import { vi } from "vitest";
    
    vi.mock("./api", () => ({
      fetchUser: vi.fn(() => 
        Promise.resolve({ id: 1, name: "Alice" })
      )
    }));
  • Mock Next.js router for navigation testing.
    vi.mock("next/router", () => ({
      useRouter: vi.fn(() => ({
        push: vi.fn(),
        pathname: "/"
      }))
    }));
  • Avoid mocking everything — only mock slow or external things.
  • Test component logic thoroughly with real implementations.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 144
Intermediate

React Testing Best Practices

(continued)

Async Testing

  • Use async/await for testing async code like API calls.
    it("displays user data after fetching", async () => {
      render(<UserProfile userId="1" />);
      
      // Component fetches user data
      const name = await screen.findByText("Alice");
      expect(name).toBeInTheDocument();
    });
  • Use findBy for elements that appear after async operations.
    // findBy waits for the element to appear
    const element = await screen.findByText("Loaded");
    
    // getBy fails immediately if element doesn't exist
    expect(() => screen.getByText("Loaded")).toThrow();
  • Use waitFor for complex async scenarios.
    await waitFor(() => {
      expect(screen.getByText("Success")).toBeInTheDocument();
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 145
Intermediate

React Testing Best Practices

(continued)

Common Testing Patterns

  • Test form submission with userEvent.
    it("submits form with email", async () => {
      const user = userEvent.setup();
      render(<LoginForm />);
      
      await user.type(screen.getByLabelText("Email"), "test@example.com");
      await user.type(screen.getByLabelText("Password"), "password");
      await user.click(screen.getByRole("button", { name: /login/i }));
      
      expect(screen.getByText("Welcome")).toBeInTheDocument();
    });
  • Test conditional rendering based on props.
    it("shows success message when isSuccess is true", () => {
      render(<Alert isSuccess={true} message="All good!" />);
      expect(screen.getByText("All good!")).toBeInTheDocument();
    });
  • Test error handling and edge cases.
    it("shows error when API fails", async () => {
      vi.mocked(fetchUser).mockRejectedValue(new Error("API failed"));
      render(<UserProfile userId="1" />);
      
      expect(await screen.findByText("Error loading user")).toBeInTheDocument();
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Testing Best Practices
Chapter 20 · Page 146
Intermediate

React Testing Best Practices

(FAQ)

FAQ

React Testing Library is the current standard and is officially recommended by the React team. Enzyme is largely unmaintained and encourages querying internal state and component structure, which makes tests fragile when you refactor.

Use findBy* queries (e.g., await screen.findByText('Loaded')) or wrap assertions in await waitFor(() => expect(...)) after triggering the action. For realistic network mocking across many tests, consider Mock Service Worker (msw) instead of per-test fetch stubs.

userEvent simulates the full sequence of browser events a real user triggers (e.g., pointerover, focus, keydown, input, keyup on a type), while fireEvent dispatches a single synthetic event. Use userEvent from @testing-library/user-event for interactions like typing and clicking to catch bugs that depend on event ordering.

Call jest.mock('axios') at the top of your test file, then configure return values with axios.get.mockResolvedValue({ data: yourData }) per test. Reset mocks between tests using jest.clearAllMocks() in a beforeEach to prevent state from leaking across test cases.

Use getBy* when the element must exist synchronously (throws if missing), queryBy* when asserting an element is absent (returns null instead of throwing), and findBy* for elements that appear asynchronously (returns a promise). Prefer queries in this priority order: ByRole, ByLabelText, ByPlaceholderText, ByText — they mirror how users and assistive technology discover elements.

Useful Cheatsheetsusefulcheatsheets.com
React useContext Advanced Patterns
Chapter 21 · Page 147
Intermediate

React useContext Advanced Patterns

Advanced patterns for Context API, custom hooks, and state management.

TL;DR

  1. 01Use useSyncExternalStore to build fine-grained context subscriptions that skip unnecessary re-renders.
  2. 02Wrap components in a provider wrapper in tests to keep context consumers testable in isolation.
  3. 03Client-only context does not work in Server Components — pass data via props or use cookies/headers.

Tips

  1. 01Export a createTestWrapper function from your test utils that returns a wrapper component — this integrates cleanly with React Testing Library's wrapper option.
  2. 02Always expose async operations through the custom hook, not raw context — it keeps the async logic centralized.

Warnings

  1. 01This pattern requires careful implementation — prefer Zustand or Jotai in production for battle-tested selector behavior without the boilerplate.
  2. 02Never import a Server Component inside a Client Component — this breaks the React Server Component model. Server Components can receive Client Providers as children though.
  3. 03Don't over-fetch in Context providers — prefer React Query for server data and reserve Context for client-side state.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Advanced Patterns
Chapter 21 · Page 148
Intermediate

React useContext Advanced Patterns

(continued)

Custom Context Hooks

  • Wrap useContext in a custom hook to simplify access and centralise the context logic.

    const UserContext = createContext<{ user: User | null; setUser: (u: User | null) => void } | undefined>(undefined);
    
    export function UserProvider({ children }: { children: React.ReactNode }) {
      const [user, setUser] = useState<User | null>(null);
      return (
        <UserContext.Provider value={{ user, setUser }}>
          {children}
        </UserContext.Provider>
      );
    }
    
    export function useUser() {
      const context = useContext(UserContext);
      if (!context) throw new Error("useUser must be used inside UserProvider");
      return context;
    }
    
  • Throw a descriptive error in the hook when the context is missing to catch misconfigured trees early.

    export function useTheme() {
      const ctx = useContext(ThemeContext);
      if (ctx === undefined) {
        throw new Error("useTheme must be called inside ThemeProvider");
      }
      return ctx;
    }
    
  • Add derived values inside the hook so consumers never repeat the same logic.

    export function useAuth() {
      const { user } = useContext(AuthContext)!;
      return {
        user,
        isLoggedIn: !!user,             // derived
        isAdmin: user?.role === "admin", // derived
        displayName: user?.name ?? "Guest", // derived
      };
    }
    
  • Keep the raw context private — only export the provider and the custom hook.

    // auth-context.tsx
    const AuthContext = createContext<AuthState | undefined>(undefined); // NOT exported
    
    export function AuthProvider({ children }: { children: React.ReactNode }) { /* ... */ }
    export function useAuth() { return useContext(AuthContext)!; } // only hook exported
    
  • Export a single hook per context so consumers never call useContext directly.

    // consumers call the hook, not useContext — implementation stays hidden
    function Profile() {
      const { user, isAdmin } = useAuth(); // clean, type-safe API
      return <div>{isAdmin ? "Admin: " : ""}{user?.name}</div>;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Advanced Patterns
Chapter 21 · Page 149
Intermediate

React useContext Advanced Patterns

(continued)

Context Selectors

  • Use useSyncExternalStore to build a selector pattern that only re-renders consumers when their selected slice changes.

    import { createContext, useContext, useRef, useSyncExternalStore } from "react";
    
    type Store<T> = { get: () => T; set: (val: Partial<T>) => void; subscribe: (cb: () => void) => () => void };
    
    function createStore<T>(initial: T): Store<T> {
      let state = initial;
      const listeners = new Set<() => void>();
      return {
        get: () => state,
        set: (partial) => { state = { ...state, ...partial }; listeners.forEach(l => l()); },
        subscribe: (cb) => { listeners.add(cb); return () => listeners.delete(cb); }
      };
    }
    
    const StoreContext = createContext<Store<{ count: number; user: string }>>(null!);
    
    // Selector hook — only re-renders when the selected value changes
    function useStore<R>(selector: (state: { count: number; user: string }) => R): R {
      const store = useContext(StoreContext);
      return useSyncExternalStore(store.subscribe, () => selector(store.get()));
    }
    
    // Only re-renders when count changes, ignores user changes
    function Counter() {
      const count = useStore(s => s.count);
      return <p>{count}</p>;
    }
    
  • This pattern mimics Zustand's selector behavior inside React Context — useful when you want React Context ergonomics without an external library.

  • Compare to naive Context: without selectors, every consumer re-renders on any context change; with selectors, only consumers whose selected value changed re-render.

Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Advanced Patterns
Chapter 21 · Page 150
Intermediate

React useContext Advanced Patterns

(continued)

Testing Context Providers

  • Wrap components in their providers inside a test render helper so each test controls exactly what context the component receives.

    // test-utils.tsx
    import { render } from "@testing-library/react";
    import { UserProvider } from "../contexts/UserContext";
    import { ThemeProvider } from "../contexts/ThemeContext";
    
    export function renderWithProviders(ui: React.ReactElement, {
      user = null,
      theme = "light"
    } = {}) {
      return render(
        <UserProvider initialUser={user}>
          <ThemeProvider initialTheme={theme}>
            {ui}
          </ThemeProvider>
        </UserProvider>
      );
    }
    
  • Pass initial values to providers via props so tests can control state without mocking global modules.

    // In your provider, accept initial state as a prop
    export function UserProvider({ children, initialUser = null }) {
      const [user, setUser] = useState(initialUser);
      return <UserContext.Provider value={{ user, setUser }}>{children}</UserContext.Provider>;
    }
    
  • Test components that consume context by rendering them through the helper.

    test("shows welcome message for logged-in user", () => {
      const { getByText } = renderWithProviders(<Navbar />, {
        user: { name: "Alice", role: "admin" }
      });
      expect(getByText("Welcome, Alice")).toBeInTheDocument();
    });
    
  • Test the provider itself by rendering a consumer inside it and interacting with it.

    test("login updates user context", async () => {
      const { getByRole, findByText } = render(
        <UserProvider><LoginForm /><Navbar /></UserProvider>
      );
      await userEvent.click(getByRole("button", { name: "Login" }));
      expect(await findByText("Welcome, Alice")).toBeInTheDocument();
    });
    
  • Avoid mocking useContext directly — testing through real providers verifies that the full provider+consumer integration works correctly.

Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Advanced Patterns
Chapter 21 · Page 151
Intermediate

React useContext Advanced Patterns

(continued)

Context and Server Components

  • React Context requires a client component — Server Components cannot call useContext or render a <Provider>.

    // app/layout.tsx — Server Component (default in App Router)
    // Cannot use createContext or useContext here directly
    export default function RootLayout({ children }) {
      return <html><body><ClientProviders>{children}</ClientProviders></body></html>;
    }
    
  • Extract providers into a dedicated client component so the root layout stays a Server Component.

    // app/providers.tsx
    "use client";
    import { ThemeProvider } from "./contexts/ThemeContext";
    import { UserProvider } from "./contexts/UserContext";
    
    export function ClientProviders({ children }: { children: React.ReactNode }) {
      return (
        <ThemeProvider>
          <UserProvider>{children}</UserProvider>
        </ThemeProvider>
      );
    }
    
  • Pass initial data from Server Components to Context providers via props — fetch on the server, hydrate on the client.

    // app/layout.tsx (Server Component)
    import { getUser } from "@/lib/auth";
    import { ClientProviders } from "./providers";
    
    export default async function RootLayout({ children }) {
      const user = await getUser(); // server-side fetch
      return (
        <html><body>
          <ClientProviders initialUser={user}>{children}</ClientProviders>
        </body></html>
      );
    }
    
  • Use cookies() or headers() from next/headers in Server Components instead of Context for data that doesn't need client reactivity.

    import { cookies } from "next/headers";
    export default async function Page() {
      const theme = (await cookies()).get("theme")?.value ?? "light";
      return <div data-theme={theme}>...</div>;
    }
    
  • Server Components can be children of Client Context providers — they receive the context value when they re-render on the client.

Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Advanced Patterns
Chapter 21 · Page 152
Intermediate

React useContext Advanced Patterns

(continued)

Context with Async Operations

  • Define an async login function inside the provider and expose it through the context value.

    export function UserProvider({ children }: { children: React.ReactNode }) {
      const [user, setUser] = useState<User | null>(null);
      const [loading, setLoading] = useState(false);
    
      const login = async (email: string, password: string) => {
        setLoading(true);
        try {
          const result = await authenticate(email, password); // API call
          setUser(result);
        } finally {
          setLoading(false);
        }
      };
    
      return (
        <UserContext.Provider value={{ user, login, loading }}>
          {children}
        </UserContext.Provider>
      );
    }
    
  • Store loading and error alongside data in the context value so every consumer can react to async state.

    const [state, setState] = useState<{
      data: User | null;
      loading: boolean;
      error: string | null;
    }>({ data: null, loading: false, error: null });
    
    // Expose all three so consumers render loading/error UI without local state
    <DataContext.Provider value={state}>{children}</DataContext.Provider>
    
  • Dispatch async actions through useReducer to keep state transitions predictable and testable.

    type Action =
      | { type: "FETCH_START" }
      | { type: "FETCH_SUCCESS"; payload: User }
      | { type: "FETCH_ERROR"; payload: string };
    
    const [state, dispatch] = useReducer(asyncReducer, initialState);
    
    async function fetchUser(id: string) {
      dispatch({ type: "FETCH_START" });
      try {
        const user = await getUser(id);
        dispatch({ type: "FETCH_SUCCESS", payload: user });
      } catch (e) {
        dispatch({ type: "FETCH_ERROR", payload: (e as Error).message });
      }
    }
    
  • Cancel in-flight async operations on unmount to prevent state updates on unmounted providers.

    useEffect(() => {
      const controller = new AbortController();
    
      async function load() {
        try {
          const res = await fetch("/api/user", { signal: controller.signal });
          setUser(await res.json());
        } catch (e) {
          if ((e as Error).name !== "AbortError") setError("Failed to load");
        }
      }
    
      load();
      return () => controller.abort(); // cancel on unmount
    }, []);
    
  • Expose async methods via the custom hook so callers never interact with raw context directly.

    export function useUser() {
      const ctx = useContext(UserContext);
      if (!ctx) throw new Error("useUser must be used inside UserProvider");
    
      // Wrap async method to keep error handling in one place
      const safeLogin = async (email: string, password: string) => {
        await ctx.login(email, password);
      };
    
      return { ...ctx, login: safeLogin };
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Advanced Patterns
Chapter 21 · Page 153
Intermediate

React useContext Advanced Patterns

(FAQ)

FAQ

Split your context by domain so components only subscribe to the slice of state they need. For example, keep user auth in one context and theme preferences in another — updating auth won't re-render theme consumers.

Reach for useReducer when your context state has multiple sub-values that change together or when state transitions depend on previous state. It also makes it easier to share both state and dispatch through the same context.

Pass an initializer function as the second argument to useReducer, or compute the initial value inside a useState initializer function — this runs only once on mount rather than on every render.

Create a custom hook like useAuth() that calls useContext internally and throws a descriptive error if used outside the provider. This catches misconfigured component trees early and keeps consumer code clean.

Store both data and loading/error status in the context state, then trigger fetches via useEffect inside the provider component. Expose the status alongside the data so consumers can react to loading and error states without managing them locally.

Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 154
Intermediate

React useContext Hook

Share state across React components without prop drilling using the useContext hook.

TL;DR

  1. 01Create a Context with createContext for shared data.
  2. 02Wrap components with a Provider to make data available.
  3. 03Use useContext to access data in any child component.

Tips

  1. 01Create custom hooks for contexts to simplify usage and add error checking for consumers.

Warnings

  1. 01Every context change causes all consumers to re-render — split contexts by concern to avoid performance issues.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 155
Intermediate

React useContext Hook

(continued)

Creating Context

  • Create a context to hold shared data.
    const ThemeContext = React.createContext();
  • Provide an initial value as a fallback when no provider wraps the consumer.
    const UserContext = React.createContext({
      user: null,
      setUser: () => {}
    });
  • Export the context for use in other components.
    export const ThemeContext = React.createContext();
  • Keep context creation in its own file for clarity.
    // theme-context.js
    import { createContext } from "react";
    export const ThemeContext = createContext("light");
  • Use TypeScript generics to type the context value.
    interface User { id: number; name: string; }
    interface UserContextType { user: User | null; setUser: (u: User) => void; }
    
    const UserContext = createContext<UserContextType | undefined>(undefined);
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 156
Intermediate

React useContext Hook

(continued)

Creating a Provider

  • Wrap your app with a Provider component.
    function App() {
      const [theme, setTheme] = useState("light");
      
      return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
          <Header />
          <Main />
          <Footer />
        </ThemeContext.Provider>
      );
    }
  • Provider passes value to all child components.
    function Layout({ children }) {
      const [user, setUser] = useState(null);
      
      return (
        <UserContext.Provider value={{ user, setUser }}>
          {children}
        </UserContext.Provider>
      );
    }
  • Extract the provider into its own component to keep App clean.
    export function ThemeProvider({ children }) {
      const [theme, setTheme] = useState("light");
      return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
          {children}
        </ThemeContext.Provider>
      );
    }
  • Memoize the context value to avoid re-rendering every consumer.
    function UserProvider({ children }) {
      const [user, setUser] = useState(null);
      const value = useMemo(() => ({ user, setUser }), [user]);
      return (
        <UserContext.Provider value={value}>
          {children}
        </UserContext.Provider>
      );
    }
  • Nest multiple providers at the top of the tree.
    function App() {
      return (
        <AuthProvider>
          <ThemeProvider>
            <Router>
              <AppRoutes />
            </Router>
          </ThemeProvider>
        </AuthProvider>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 157
Intermediate

React useContext Hook

(continued)

Consuming Context

  • Access context with useContext hook.
    function Header() {
      const { theme, setTheme } = useContext(ThemeContext);
      
      return (
        <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
          Current theme: {theme}
        </button>
      );
    }
  • useContext works in any descendant component, no matter how deep.
    function DeepComponent() {
      const { user } = useContext(UserContext);
      return <p>User: {user?.name}</p>;
    }
  • Returns the createContext default value if no matching Provider is above it, or undefined if no default was set.
  • Destructure only the values you need to be explicit.
    // Clear: names the exact values this component uses
    const { theme } = useContext(ThemeContext);
  • Re-renders happen whenever the context value changes.
    // This component re-renders every time theme or setTheme changes
    const { theme, setTheme } = useContext(ThemeContext);
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 158
Intermediate

React useContext Hook

(continued)

Custom Hooks

  • Create a custom hook to simplify context access.
    function useTheme() {
      const context = useContext(ThemeContext);
      if (!context) {
        throw new Error("useTheme must be inside ThemeProvider");
      }
      return context;
    }
    
    function Header() {
      const { theme, setTheme } = useTheme();
      return <button onClick={() => setTheme("dark")}>{theme}</button>;
    }
  • Custom hooks provide error checking and a cleaner API.
  • Makes consuming context easier for other developers.
  • Add computed values or helpers inside the custom hook.
    function useAuth() {
      const { user } = useContext(AuthContext);
      return {
        user,
        isLoggedIn: !!user,
        isAdmin: user?.role === "admin"
      };
    }
  • Export only the custom hook, not the raw context.
    // users import useUser, not UserContext directly
    export { useUser };       // export hook
    // don't export UserContext — keep it private
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 159
Intermediate

React useContext Hook

(continued)

Best Practices

  • Place the Provider as close as possible to the components that need it, not always at the app root.
    // Scope SidebarContext to the sidebar, not the whole app
    function Sidebar() {
      return (
        <SidebarContext.Provider value={collapsed}>
          <SidebarPanel />
        </SidebarContext.Provider>
      );
    }
  • Colocate context with the feature that owns it instead of lifting everything to a shared global file — move it up only when a second, unrelated feature genuinely needs the same data.
    // features/cart/cart-context.jsx — lives next to the feature, not in a top-level contexts/ folder
    export const CartContext = createContext(null);
  • Avoid context for state that changes on every keystroke or frame, like form input or animation values — high-frequency updates re-render every consumer and are a poor fit for the context model.
    // Fast-changing values fit local state or a state library better than context
    const [query, setQuery] = useState(""); // keep local, not in context
  • Prefer local state or props for data used by only 1–2 components.
    // Don't reach for Context just to avoid one prop
    // Context is useful for truly global data: auth, theme, locale
  • Test context consumers by wrapping them in the provider, not by mocking the context module.
    test("shows username from context", () => {
      render(
        <UserContext.Provider value={{ user: { name: "Alice" }, setUser: jest.fn() }}>
          <UserDisplay />
        </UserContext.Provider>
      );
      expect(screen.getByText("Alice")).toBeInTheDocument();
    });
  • Build a small reusable test helper that wraps render with your real providers, so every test exercises the same provider tree your app uses in production.
    function renderWithProviders(ui) {
      return render(<AuthProvider><ThemeProvider>{ui}</ThemeProvider></AuthProvider>);
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 160
Intermediate

React useContext Hook

(FAQ)

FAQ

Use useContext when data needs to be accessed by many components at different nesting levels, such as theme, locale, or auth state. Prop drilling becomes a maintenance burden once you're passing props through more than 2-3 layers of components that don't use the data themselves.

All consumers re-render whenever any value in the context object changes. Split your context into smaller, focused contexts by concern (e.g., separate AuthContext from ThemeContext) so that a change in one doesn't trigger re-renders in unrelated consumers.

Include an updater function in your context value alongside the data — pass a useState setter or a dispatch function from useReducer. Consumers can then call that function to trigger state changes that propagate back through the Provider.

useContext is built-in and ideal for low-frequency updates like theme or auth, with no extra dependencies. Redux adds middleware, devtools, and a strict unidirectional flow suited to complex, high-frequency global state — useContext alone can become hard to scale when many unrelated pieces of state share a single context.

In your custom hook, check that the returned context value is not undefined and throw a descriptive error if it is — for example: if (!ctx) throw new Error('useAuth must be used within an AuthProvider'). This gives a clear error instead of a silent undefined crash deep in the component tree.

Useful Cheatsheetsusefulcheatsheets.com
React useEffect Hook
Chapter 23 · Page 161
Intermediate

React useEffect Hook

Handle side effects, manage dependencies, and clean up after effects.

TL;DR

  1. 01useEffect runs code after render for side effects like fetching data.
  2. 02Dependency array controls when the effect runs.
  3. 03Return a cleanup function to prevent memory leaks.

Tips

  1. 01Always include all dependencies in the dependency array — ESLint's exhaustive-deps rule helps catch missing ones.

Warnings

  1. 01Forgetting cleanup functions causes memory leaks — always clean up subscriptions, timers, and listeners.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useEffect Hook
Chapter 23 · Page 162
Intermediate

React useEffect Hook

(continued)

Basic useEffect

  • Run a side effect after every render by calling useEffect with no dependency array.

    useEffect(() => {
      console.log("Component rendered"); // runs after every render
    });
    
  • Update the document title after each render as a common side-effect pattern.

    useEffect(() => {
      document.title = `You clicked ${count} times`;
      // Runs after every render where count may have changed
    });
    
  • Wrap async logic in an inner function because the effect callback itself cannot be async.

    useEffect(() => {
      async function loadData() {
        const res = await fetch("/api/users");
        const data = await res.json();
        setUsers(data);
      }
      loadData(); // call the async function immediately
    }, []);
    
  • Use useEffect for imperative side effects like logging, analytics, or third-party integrations.

    useEffect(() => {
      analytics.track("page_view", { page: pathname }); // fire-and-forget side effect
    }, [pathname]); // re-fires when the route changes
    
  • Keep effect callbacks synchronous at the top level and wrap any async work inside.

    // Wrong: async effect callback — cleanup return value is lost
    useEffect(async () => { await doSomething(); }, []);
    
    // Right: inner async function with explicit call
    useEffect(() => {
      async function run() { await doSomething(); }
      run();
    }, []);
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useEffect Hook
Chapter 23 · Page 163
Intermediate

React useEffect Hook

(continued)

Dependency Comparison Rules

  • Know that React compares each dependency to its previous value with Object.is, not a deep equality check — this is the exact mechanism that decides whether an effect re-runs.

    useEffect(() => {
      console.log("Count changed:", count);
      // Object.is(prevCount, count) === false triggers a re-run
    }, [count]);
    
  • Expect object and array literals to fail Object.is every render because a new reference is created each time, even when the contents are identical.

    useEffect(() => {
      fetchUserData(filters); // filters = { status: "active" } looks "changed" every render
    }, [filters]); // new object reference each render → effect re-fires constantly
    
  • Fix reference-instability by memoizing the object/array with useMemo or by destructuring to primitive dependencies instead.

    useEffect(() => {
      fetchUserData(status, token); // primitives compare correctly with Object.is
    }, [status, token]); // both must appear in the deps array
    
  • Omit the array entirely only when the effect must run after every single render — each item is still diffed with Object.is, there's just no array to skip the check.

    useEffect(() => {
      // Runs after EVERY render — use only when you truly need this
      syncStateToExternalSystem(value);
    }); // no array = every render
    
  • Enable the ESLint react-hooks/exhaustive-deps rule so it statically flags any variable read inside the effect that's missing from the array — it does not understand Object.is semantics, only static usage.

    # Install the rules-of-hooks plugin
    npm install eslint-plugin-react-hooks --save-dev
    # Add to .eslintrc: "react-hooks/exhaustive-deps": "warn"
    # Use the rule's quick-fix to auto-insert missing deps, then verify literals are memoized
    
  • Avoid silencing exhaustive-deps with an inline disable comment — it almost always hides a stale-closure bug rather than a false positive.

    useEffect(() => {
      fetchUserData(userId); // eslint-disable-next-line react-hooks/exhaustive-deps
      // Tempting but dangerous: userId changes won't re-trigger this effect
    }, []);
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useEffect Hook
Chapter 23 · Page 164
Intermediate

React useEffect Hook

(continued)

Data Fetching

  • Fetch data when the component mounts by using an empty dependency array.

    useEffect(() => {
      fetch("/api/data")
        .then((r) => r.json())
        .then(setData);
    }, []); // runs once on mount
    
  • Track loading and error state alongside the fetch for complete async UI handling.

    useEffect(() => {
      setLoading(true);
      fetch(url)
        .then((r) => r.json())
        .then(setData)
        .catch(setError)
        .finally(() => setLoading(false));
    }, [url]); // re-fetches when url changes
    
  • Cancel stale requests with AbortController to prevent setting state after unmount.

    useEffect(() => {
      const controller = new AbortController();
      fetch(url, { signal: controller.signal })
        .then((r) => r.json())
        .then(setData)
        .catch((err) => {
          if (err.name !== "AbortError") setError(err); // ignore intentional cancellation
        });
      return () => controller.abort(); // cancel on unmount or url change
    }, [url]);
    
  • Re-fetch automatically when a dependency like userId changes by including it in the array.

    useEffect(() => {
      async function load() {
        const res = await fetch(`/api/users/${userId}`);
        setUser(await res.json());
      }
      load();
    }, [userId]); // re-runs every time userId changes
    
  • Use SWR or React Query in production for caching, deduplication, and automatic revalidation.

    import useSWR from "swr";
    
    // SWR handles loading state, caching, and revalidation on focus
    const { data, error, isLoading } = useSWR("/api/user", fetcher);
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useEffect Hook
Chapter 23 · Page 165
Intermediate

React useEffect Hook

(continued)

Cleanup Functions

  • Return a function from the effect callback itself — this is the only way React knows it's a cleanup function, and it fires before unmount or before the next effect run.

    useEffect(() => {
      const subscription = subscribe((data) => {
        setData(data);
      });
      return () => subscription.unsubscribe(); // cleanup before unmount
    }, []);
    
  • Remember that the cleanup function closes over the props/state values from the render that scheduled it, not the latest render — it always sees the OLD values, never the current ones.

    useEffect(() => {
      const id = roomId; // captured by this closure
      connect(id);
      return () => disconnect(id); // disconnects the OLD roomId, even after roomId changes
    }, [roomId]);
    
  • Picture the exact ordering on every dependency change: React runs the PREVIOUS effect's cleanup first, fully, before invoking the NEW effect's setup — never interleaved.

    useEffect(() => {
      const timer = setInterval(() => setTime((t) => t + 1), 1000);
      return () => clearInterval(timer); // old timer fully cleared before a new one starts
    }, [intervalMs]); // change in intervalMs: cleanup(old) → setup(new), in that order
    
  • Pair every subscription-style API call with its exact inverse in cleanup — addEventListener/removeEventListener, connect/disconnect, open/close — so each effect run leaves zero residue for the next one to build on.

    useEffect(() => {
      const handleResize = () => setWidth(window.innerWidth);
      window.addEventListener("resize", handleResize);
      return () => window.removeEventListener("resize", handleResize); // exact inverse of setup
    }, []);
    
  • Treat a missing cleanup return as a silent bug, not a no-op — React still re-runs the new effect on every dependency change, it just never undoes the old one first.

    useEffect(() => {
      const ws = new WebSocket(`wss://example.com/${roomId}`);
      ws.onmessage = (e) => setMessages((prev) => [...prev, e.data]);
      return () => ws.close(); // without this, every roomId change leaks an open socket
    }, [roomId]);
    
  • Re-subscribe correctly when a dependency like userId changes by relying on cleanup to unsubscribe from the PREVIOUS value before the new effect subscribes to the current one.

    useEffect(() => {
      const sub = subscribe(userId); // subscribe to new user
      return () => sub.unsubscribe(); // unsubscribe from PREVIOUS user first
    }, [userId]); // runs cleanup on every userId change before re-subscribing
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useEffect Hook
Chapter 23 · Page 166
Intermediate

React useEffect Hook

(continued)

useEffect Patterns

  • Sync state with localStorage by reading on mount and writing when state changes.

    useEffect(() => {
      const saved = localStorage.getItem("theme");
      if (saved) setTheme(saved); // restore on mount
    }, []);
    
    useEffect(() => {
      localStorage.setItem("theme", theme); // persist on every change
    }, [theme]);
    
  • Track the previous value of a variable by storing it in a ref before each update.

    const prevCountRef = useRef<number | undefined>(undefined);
    
    useEffect(() => {
      prevCountRef.current = count; // store current as previous after each render
    }, [count]);
    
    const prevCount = prevCountRef.current; // previous value available during render
    
  • Use multiple separate useEffect calls for unrelated concerns to keep each effect focused.

    useEffect(() => { /* handle auth token refresh */ }, [token]);
    useEffect(() => { /* sync theme to body class */ }, [theme]);
    useEffect(() => { /* track window resize */ }, []);
    // Three concerns → three effects, each independently managed
    
  • Focus an input or dialog element after it becomes visible in the DOM.

    useEffect(() => {
      if (isOpen && inputRef.current) {
        inputRef.current.focus(); // focus runs after the DOM updates
      }
    }, [isOpen]); // re-runs whenever isOpen toggles to true
    
  • Update the document title reactively to reflect dynamic values like unread counts.

    useEffect(() => {
      document.title = unreadCount > 0
        ? `(${unreadCount}) My App`
        : "My App";
    }, [unreadCount]); // updates whenever unreadCount changes
    
  • Treat Strict Mode's extra dev-only mount cycle as a debugging aid, not a bug, when writing custom hooks or subscriptions.

    function useChatRoom(roomId) {
      useEffect(() => {
        const connection = createConnection(roomId);
        connection.connect();
        return () => connection.disconnect(); // must fully undo connect()
      }, [roomId]); // Strict Mode connects, disconnects, reconnects in dev to prove cleanup is correct
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useEffect Hook
Chapter 23 · Page 167
Intermediate

React useEffect Hook

(FAQ)

FAQ

useEffect runs after the browser has painted the screen, not during render. This makes it safe for DOM mutations and async operations without blocking the UI.

In development, React 18+ Strict Mode intentionally runs setup, cleanup, then setup again for every effect on mount — even with an empty dependency array. This is not a bug: it simulates remounting to surface effects with missing or broken cleanup. Production builds always run the effect once. Fix the underlying issue (incomplete cleanup) rather than trying to suppress the double run.

An infinite loop usually means an object or array is listed as a dependency but gets recreated on every render, so Object.is sees a new reference each time. Move the value outside the component, memoize it with useMemo/useCallback, or restructure so only primitives are in the dependency array.

useLayoutEffect fires synchronously after DOM mutations but before the browser paints, so it blocks rendering — use it only when you need to measure or mutate the DOM before the user sees it. Prefer useEffect for everything else.

Use an AbortController: create it at the top of the effect, pass signal to fetch, and call controller.abort() in the cleanup function. This prevents setState from being called on an unmounted component when the request resolves late.

Useful Cheatsheetsusefulcheatsheets.com
React useMemo and useCallback
Chapter 24 · Page 168
Intermediate

React useMemo and useCallback

Optimize React performance by memoizing values and callbacks to prevent unnecessary re-renders.

TL;DR

  1. 01Use useMemo to cache expensive calculations between renders.
  2. 02Use useCallback to keep function references stable for optimization.
  3. 03Pass a dependency array to control when memoization resets.

Tips

  1. 01Use the React DevTools Profiler to measure actual performance gains before and after memoization, since it adds overhead.

Warnings

  1. 01Missing dependencies in the array causes stale closures and subtle bugs, so enable the ESLint rule to catch them automatically.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useMemo and useCallback
Chapter 24 · Page 169
Intermediate

React useMemo and useCallback

(continued)

useMemo Basics

  • Import useMemo from React at the top of your component file.
    import { useMemo } from "react";
  • Wrap an expensive calculation to cache its result between renders.
    const total = useMemo(() => {
      return items.reduce((sum, item) => sum + item.price, 0);
    }, [items]);
  • The hook returns the cached value, not the function itself.
  • Use a dependency array to reset the cache when inputs change.
  • Only use useMemo when the calculation is slow and frequent.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useMemo and useCallback
Chapter 24 · Page 170
Intermediate

React useMemo and useCallback

(continued)

useCallback Basics

  • Import useCallback from React at the top of your component file.
    import { useCallback } from "react";
  • Memoize a callback function so its reference stays the same between renders.
    const handleClick = useCallback(() => {
      console.log("clicked");
    }, []);
  • Pass the memoized function to child components expecting stable references.
  • Use a dependency array that includes every value the callback reads.
  • The reference only changes when a dependency changes.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useMemo and useCallback
Chapter 24 · Page 171
Intermediate

React useMemo and useCallback

(continued)

Dependency Arrays

  • An empty dependency array [] memoizes forever, never resetting.
    const value = useMemo(() => expensive(), []);
  • Include values that the calculation depends on in the array.
    const filtered = useMemo(() => filter(items, search), [items, search]);
  • The cache resets when any dependency changes, then recalculates fresh.
  • Forgetting a dependency can cause stale values and hard-to-find bugs.
  • Use ESLint rules to catch missing dependencies automatically.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useMemo and useCallback
Chapter 24 · Page 172
Intermediate

React useMemo and useCallback

(continued)

When to Use Them

  • Use useMemo only for calculations that noticeably slow down rendering.
  • Use useCallback when passing functions to optimized child components.
  • Use React.memo() alongside useCallback for component memoization.
  • Avoid premature optimization — measure first with DevTools profiler.
  • Do not memoize simple values or callbacks — the overhead costs more.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useMemo and useCallback
Chapter 24 · Page 173
Intermediate

React useMemo and useCallback

(continued)

Common Patterns

  • Memoize object literals passed as props to prevent child re-renders.
    const config = useMemo(() => ({
      timeout: 5000, retries: 3
    }), []);
  • Use useCallback with useEffect to avoid infinite loops.
    const fetch = useCallback(async () => {
      const res = await api.get();
      setData(res);
    }, []);
  • Combine with useMemo to memoize complex derived state.
  • Watch the React DevTools profiler to confirm memoization helps.
  • Consider using a state management library for complex memoization needs.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useMemo and useCallback
Chapter 24 · Page 174
Intermediate

React useMemo and useCallback

(FAQ)

FAQ

useMemo caches the return value of a function, while useCallback caches the function itself. Use useMemo for expensive computations and useCallback when you need a stable function reference to pass as a prop or dependency.

Only memoize when the computation is genuinely expensive (e.g., filtering large arrays, complex math) and runs on frequent re-renders. For simple transformations, the overhead of memoization outweighs any benefit.

This is a stale closure caused by missing dependencies in the dependency array. Any variable from the component scope that the function references must be listed in the array, or the callback will capture its initial value indefinitely.

No — it can actually hurt performance by adding memory and comparison overhead. useCallback is only beneficial when passing functions to memoized child components (React.memo) or when the function is listed as a dependency in another hook like useEffect.

List each prop used in the computation as a dependency: const result = useMemo(() => expensiveCalc(propA, propB), [propA, propB]). The memoized value recomputes only when those specific props change, not on every render.

Useful Cheatsheetsusefulcheatsheets.com
React useReducer Hook
Chapter 25 · Page 175
Intermediate

React useReducer Hook

Manage complex state logic with the useReducer hook, actions, reducers, and common patterns.

TL;DR

  1. 01Use useReducer for state with several related update rules.
  2. 02Write a reducer function that handles each action type.
  3. 03Dispatch actions to trigger state changes in components.

Tips

  1. 01Pair <code>useReducer</code> with <code>useContext</code> to build a clean global store without pulling in Redux or Zustand for small apps.

Warnings

  1. 01Never mutate the state object directly inside a reducer, since React relies on a new reference to detect changes and re-render.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useReducer Hook
Chapter 25 · Page 176
Intermediate

React useReducer Hook

(continued)

Basic Usage

  • Import useReducer from React at the top of your component file.
    import { useReducer } from "react";
  • Call useReducer with a reducer function and an initial state value.
    const [state, dispatch] = useReducer(reducer, { count: 0 });
  • The hook returns the current state and a dispatch function for actions.
  • Reducers are pure functions that take state and an action, then return new state.
  • Use this pattern when state updates depend on the current state.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useReducer Hook
Chapter 25 · Page 177
Intermediate

React useReducer Hook

(continued)

Writing Reducers

  • Define a reducer function that handles each action type with a switch.
    function reducer(state, action) {
      switch (action.type) {
        case "increment":
          return { count: state.count + 1 };
        case "decrement":
          return { count: state.count - 1 };
        default:
          return state;
      }
    }
  • Always return a new state object instead of mutating the existing one.
  • Include a default case to handle unknown action types safely.
  • Keep reducers pure — no API calls, timers, or random values inside.
  • This makes state changes predictable and easy to test.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useReducer Hook
Chapter 25 · Page 178
Intermediate

React useReducer Hook

(continued)

Dispatching Actions

  • Call dispatch with an action object to trigger a state update.
    <button onClick={() => dispatch({ type: "increment" })}>
      +
    </button>
  • Actions are plain objects with a required type field.
  • Add extra fields to pass data along with the action.
    dispatch({ type: "add", payload: 5 });
  • The reducer reads these fields to compute the new state.
  • Dispatch can be passed down to child components through props.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useReducer Hook
Chapter 25 · Page 179
Intermediate

React useReducer Hook

(continued)

When to Use It

  • Reach for useReducer when state has many related update paths.
  • Use it when the next state depends on multiple pieces of current state.
  • Use it when actions need to be predictable and easy to test.
  • Use useState for simple booleans, strings, and counters instead.
  • Use Context plus reducer for app-wide state without external libraries.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useReducer Hook
Chapter 25 · Page 180
Intermediate

React useReducer Hook

(continued)

Common Patterns

  • Initialize lazily with a third argument for expensive setup logic.
    const [state, dispatch] = useReducer(reducer, props, (p) => ({
      count: p.initial
    }));
  • Type actions with a discriminated union in TypeScript for safety.
    type Action =
      | { type: "increment" }
      | { type: "add"; payload: number };
  • Pair useReducer with useContext to share state across the tree.
  • Split large reducers into smaller helper functions for clarity.
  • Use useImmerReducer from libraries when deep updates feel tedious.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useReducer Hook
Chapter 25 · Page 181
Intermediate

React useReducer Hook

(FAQ)

FAQ

Reach for useReducer when your state has multiple sub-values that change together, or when the next state depends on the previous one in complex ways. If you find yourself writing several related useState calls or passing many setters down as props, useReducer is likely the cleaner choice.

It takes a reducer function and an initial state value: const [state, dispatch] = useReducer(reducer, initialState). An optional third argument lets you pass an init function to lazily compute the initial state, which is useful for expensive setup.

Use a switch statement on action.type, returning a new state object for each case and a default that returns the current state unchanged. Each case should spread the existing state and override only the fields that change: return { ...state, count: state.count + 1 }.

For small-to-medium apps it often can — combine useReducer with useContext to share state across the tree without a third-party library. Redux still has advantages for large apps that need DevTools time-travel debugging, middleware, or a single store across many independent feature modules.

React compares state by reference, so if you return the same object (or array) the component won't re-render. Always return a new object from every reducer branch — use the spread operator or Object.assign instead of modifying the existing state in place.

Useful Cheatsheetsusefulcheatsheets.com
React Component Patterns
Chapter 26 · Page 182
Advanced

React Component Patterns

Build scalable component architectures with established design patterns.

TL;DR

  1. 01Design controlled component APIs so parents own state via value and onChange props.
  2. 02Use the Provider Pattern to share state across a subtree without prop drilling.
  3. 03Pick compound components for closed sub-component sets, render props when callers control markup.

Tips

  1. 01If your component accepts both value (controlled) and defaultValue (uncontrolled), warn in development when a consumer switches between the two modes — React's own inputs do this and it prevents subtle bugs.
  2. 02Memoize the context value with useMemo when the provider re-renders frequently — otherwise every consumer re-renders even when the value has not changed.
  3. 03Export compound sub-components as static properties of the parent so the public API stays a single cohesive import instead of several loose named exports.
  4. 04When your container grows complex, extract it into a custom hook that returns { users, loading, error } — this keeps the JSX layer thin and moves all async logic to a testable function.

Warnings

  1. 01Avoid nesting render props three levels deep or overusing Context — both create tight coupling that makes components harder to test and reuse in isolation.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Component Patterns
Chapter 26 · Page 183
Advanced

React Component Patterns

(continued)

Controlled Component API

A controlled component exposes a value prop and an onChange callback so the parent owns the state entirely. This is React's "single source of truth" contract — the component never holds private internal state for the value it displays.

// Controlled input component
function TextInput({ value, onChange, placeholder }) {
  return (
    <input
      type="text"
      value={value}
      placeholder={placeholder}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

// Parent owns the state
function LoginForm() {
  const [email, setEmail] = useState('');

  return (
    <TextInput
      value={email}
      onChange={setEmail}
      placeholder="Email address"
    />
  );
}
  • Fully controlled: parent passes value and onChange — no internal state for the value.
  • Uncontrolled fallback: omit value and use a ref via useRef for one-time reads (e.g., file inputs).
  • Validation and transformation happen in the parent's onChange handler before state is updated.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Component Patterns
Chapter 26 · Page 184
Advanced

React Component Patterns

(continued)

Provider Pattern

The Provider Pattern uses React Context to broadcast shared state — such as theme, locale, or authenticated user — to any descendant without threading props through every intermediate component. Unlike compound components, the consumers can be anywhere in the subtree and do not need to be specific child types.

const ThemeContext = createContext({ mode: 'light', toggle: () => {} });

function ThemeProvider({ children }) {
  const [mode, setMode] = useState('light');

  const toggle = () =>
    setMode((m) => (m === 'light' ? 'dark' : 'light'));

  return (
    <ThemeContext.Provider value={{ mode, toggle }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Custom hook keeps consumers clean
function useTheme() {
  return useContext(ThemeContext);
}

// Deep consumer — no prop drilling needed
function ThemeToggleButton() {
  const { mode, toggle } = useTheme();
  return <button onClick={toggle}>Switch to {mode === 'light' ? 'dark' : 'light'}</button>;
}

// App wires it together
<ThemeProvider>
  <App />   {/* ThemeToggleButton anywhere inside has access */}
</ThemeProvider>
  • Pair every Provider with a custom hook (useTheme) so consumers get a clean call-site API.
  • Split contexts by update frequency — a slowly-changing locale and a rapidly-changing modal state belong in separate contexts to avoid unnecessary re-renders.
  • Contrasted with compound components: Provider Pattern is for app-wide or subtree-wide concerns; compound components are for self-contained component suites.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Component Patterns
Chapter 26 · Page 185
Advanced

React Component Patterns

(continued)

Typed Compound Components

  • At an advanced level, the interesting problem is not wiring a parent and child through Context — it is making the wiring type-safe and resilient to consumers reordering, omitting, or wrapping children. Start by typing the context value precisely and refusing a default object that could silently mask a missing provider.
    type SelectContextValue<T> = {
      value: T;
      onChange: (next: T) => void;
    };
    
    function createSelectContext<T>() {
      return createContext<SelectContextValue<T> | null>(null);
    }
    
  • A generic context factory lets Select<string> and Select<number> both get full type inference on value and onChange, instead of widening everything to any the way a single shared context would.
    const SelectContext = createSelectContext<string>();
    
    function useSelectContext() {
      const ctx = useContext(SelectContext);
      if (!ctx) throw new Error('Select.Option must be used inside <Select>');
      return ctx; // narrowed to SelectContextValue<string>, never null past this line
    }
    
  • Context-based implicit sharing is convenient but invisible — a consumer cannot tell from JSX alone which children are required versus optional. React.Children.map with cloneElement is the explicit alternative: the parent injects props directly into each child it recognizes, so the contract is visible in the child's own prop types rather than hidden behind a context read.
    function Select({ children, value, onChange }: SelectProps) {
      const items = Children.map(children, (child) => {
        if (!isValidElement<OptionProps>(child)) return child;
        return cloneElement(child, {
          selected: child.props.value === value,
          onSelect: () => onChange(child.props.value),
        });
      });
      return <div role="listbox">{items}</div>;
    }
    
  • Trade-off: cloneElement breaks if a consumer wraps an option in their own <div> or fragment — the clone only reaches direct children, so nesting silently drops the injected props. Context has no such restriction; any descendant, however deeply wrapped, can call useSelectContext(). Prefer Context for any compound API you expect consumers to wrap or rearrange; reserve cloneElement for sealed, internal component sets where you control every usage site.
  • Build a flexible API by making the parent tolerant of missing, reordered, or extra children — derive an index from Children.toArray instead of relying on prop-based IDs, so consumers can omit an item conditionally without renumbering the rest.
    const orderedIds = Children.toArray(children)
      .filter(isValidElement<OptionProps>)
      .map((child) => child.props.value);
    // Safe even if a caller does {showCanada && <Select.Option value="ca" />}
    
  • Export a discriminated union of sub-component prop types from the same module as the parent, so Select.Option usage outside <Select> is rejected by both the runtime guard and the type checker before the code ever ships.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Component Patterns
Chapter 26 · Page 186
Advanced

React Component Patterns

(continued)

Generic Render Prop APIs

  • The basic mechanic — passing a function as a prop — is table stakes; the advanced concern is making that function's signature generic so the same component works across unrelated data shapes without consumers casting or duplicating the component.
    type FetcherProps<T> = {
      url: string;
      children: (state: { data: T | null; error: Error | null; loading: boolean }) => React.ReactNode;
    };
    
    function Fetcher<T>({ url, children }: FetcherProps<T>) {
      const [state, setState] = useState<{ data: T | null; error: Error | null; loading: boolean }>(
        { data: null, error: null, loading: true }
      );
    
      useEffect(() => {
        let cancelled = false;
        fetch(url)
          .then((r) => r.json())
          .then((data: T) => !cancelled && setState({ data, error: null, loading: false }))
          .catch((error) => !cancelled && setState({ data: null, error, loading: false }));
        return () => { cancelled = true; };
      }, [url]);
    
      return <>{children(state)}</>;
    }
    
    // T is inferred at the call site — no casting needed
    <Fetcher<User[]> url="/api/users">
      {({ data, loading }) => (loading ? <Spinner /> : <UserList users={data ?? []} />)}
    </Fetcher>
    
  • The modern hooks-based alternative to this exact component is a generic useFetcher<T>(url) hook that returns the same { data, error, loading } shape — for a component-tree consumer that already has hooks available, the hook is simpler because it adds no nesting and no extra component to the React tree.
    function useFetcher<T>(url: string) {
      const [state, setState] = useState<{ data: T | null; error: Error | null; loading: boolean }>(
        { data: null, error: null, loading: true }
      );
      useEffect(() => {
        let cancelled = false;
        fetch(url).then((r) => r.json()).then((data: T) => !cancelled && setState({ data, error: null, loading: false }));
        return () => { cancelled = true; };
      }, [url]);
      return state;
    }
    
  • Render props are still the better choice in three cases the hook cannot cover: class component consumers (which cannot call hooks at all), library code that must support both hook and non-hook consumers from one component, and JSX-driven APIs (like a <Form> validation summary) where the producer needs to inject markup conditionally rather than just hand back values for the caller to render itself.
  • Watch for the classic render-prop performance trap: defining the render function inline causes React to see a new function reference every parent render, which defeats React.memo on the render-prop component itself (though it does not affect the children the function returns).
    // Bad inside a frequently re-rendering parent: new closure every render
    <MemoizedFetcher<User[]> url={url}>
      {(state) => <UserList {...state} />}
    </MemoizedFetcher>
    
    // Better: stable reference via useCallback when the producer is memoized
    const renderUsers = useCallback(
      (state: FetchState<User[]>) => <UserList {...state} />,
      []
    );
    <MemoizedFetcher<User[]> url={url}>{renderUsers}</MemoizedFetcher>
    
  • This only matters when the render-prop component itself is wrapped in React.memo — for an unmemoized producer, the inline closure costs nothing extra since the producer re-renders with its parent regardless.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Component Patterns
Chapter 26 · Page 187
Advanced

React Component Patterns

(continued)

Container/Presentational Pattern

The Container/Presentational pattern separates data-fetching logic from pure rendering. The container handles side effects, state, and API calls; the presentational component is a pure function of props with no side effects, making it trivially unit-testable and reusable across different data sources.

// Container: owns data fetching and state
function UserListContainer() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/users')
      .then((r) => r.json())
      .then((data) => { setUsers(data); setLoading(false); });
  }, []);

  if (loading) return <p>Loading...</p>;
  return <UserList users={users} />;
}

// Presentational: pure UI, no side effects
function UserList({ users }) {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
  • Container components are responsible for what data is shown; presentational components control how it looks.
  • Presentational components are easy to test with hardcoded prop data — no mocking required.
  • In a hooks world, the container is often a custom hook (useUserList) rather than a wrapper component, but the separation of concerns stays the same.
  • Useful for Storybook: presentational components can be developed and documented with mock props independent of any API.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Component Patterns
Chapter 26 · Page 188
Advanced

React Component Patterns

(FAQ)

FAQ

A controlled component accepts its current value and an onChange callback as props, giving the parent full ownership of state. This makes the component predictable and testable — the parent can validate, transform, or synchronize the value before passing it back. It matters for API design because it establishes a clear contract: the component renders what it receives and reports changes upward; it never holds private state that can drift out of sync.

Both use React Context internally, but they serve different purposes. The Provider Pattern is about broadcasting shared state (like theme or auth) to any descendant in a subtree — consumers can be deeply nested and unrelated. Compound components are a tightly scoped API where a specific set of child components (like Tabs.Tab and Tabs.Panel) cooperate with a parent through a private context. Use Provider for app-wide concerns; use compound components for self-contained UI kits.

Use render props when the consumer needs to control the rendered output for each state a component produces, like loading, error, and success markup. A custom hook only returns values and functions — it cannot inject JSX on the producer's behalf. If consumers only need data, not markup control, a custom hook is simpler and avoids the extra nesting.

The shared context defaults to null, and the access hook throws an error if it reads null, which happens whenever a sub-component renders outside its parent's provider. Typing the context as Context<T | null> in TypeScript means this misuse is also caught at compile time, not just at runtime.

Because a presentational component is a pure function of props, you can render it directly in a test with whatever props you need — no mocks, no providers, no network calls. Pass a hardcoded array of user objects and assert on the rendered output. The container (or hook) that fetches real data is tested separately with mocked fetch or an msw handler, keeping the two concerns cleanly separated.

Useful Cheatsheetsusefulcheatsheets.com
React Context Performance
Chapter 27 · Page 189
Advanced

React Context Performance

Optimize context usage to avoid unnecessary re-renders with splitting and memoization.

TL;DR

  1. 01Split contexts into separate pieces to avoid cascading re-renders.
  2. 02Memoize context values to prevent unnecessary updates.
  3. 03Use useCallback for stable function references in context.

Tips

  1. 01Use useReducer instead of useState in context providers — dispatch is always stable, so no useCallback needed.

Warnings

  1. 01Over-optimization with memoization adds complexity — only optimize contexts that are accessed by many components and update frequently.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Context Performance
Chapter 27 · Page 190
Advanced

React Context Performance

(continued)

Context Re-render Problem

  • Understand that every context consumer re-renders when any value in the context changes.

    const AppContext = createContext<{ user: User | null; theme: string } | undefined>(undefined);
    
    function AppProvider({ children }: { children: React.ReactNode }) {
      const [user, setUser] = useState<User | null>(null);
      const [theme, setTheme] = useState("light");
    
      // All consumers re-render if EITHER user or theme changes
      return (
        <AppContext.Provider value={{ user, theme, setUser, setTheme }}>
          {children}
        </AppContext.Provider>
      );
    }
    
  • Recognize that inline object literals create a new reference on every render, triggering all consumers.

    // Bad: new object on every parent render — all consumers re-render unnecessarily
    <MyContext.Provider value={{ user, setUser }}>
      {children}
    </MyContext.Provider>
    
    // Good: stable reference when user hasn't changed
    const value = useMemo(() => ({ user, setUser }), [user]);
    <MyContext.Provider value={value}>{children}</MyContext.Provider>
    
  • Profile with React DevTools before optimizing to confirm which components actually re-render.

    // Open React DevTools > Profiler tab
    // Record an interaction, then inspect the flame chart
    // Only optimize components that show unnecessary renders
    
  • Spot the problem by logging renders inside a context consumer.

    function ThemeButton() {
      const { theme } = useContext(AppContext)!;
      console.log("ThemeButton rendered"); // fires on every user change too
      return <button className={theme}>Click me</button>;
    }
    
  • Understand that splitting contexts into smaller pieces is the primary fix for this problem.

    // Instead of one big AppContext, use focused per-domain contexts:
    // UserContext → only user data
    // ThemeContext → only theme data
    // Each consumer subscribes only to what it needs
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Context Performance
Chapter 27 · Page 191
Advanced

React Context Performance

(continued)

Splitting Contexts

  • Create separate contexts for independent pieces of state so updates are isolated.

    const UserContext = createContext<{ user: User | null; setUser: (u: User | null) => void } | undefined>(undefined);
    const ThemeContext = createContext<{ theme: string; setTheme: (t: string) => void } | undefined>(undefined);
    
    function AppProvider({ children }: { children: React.ReactNode }) {
      const [user, setUser] = useState<User | null>(null);
      const [theme, setTheme] = useState("light");
      return (
        <UserContext.Provider value={{ user, setUser }}>
          <ThemeContext.Provider value={{ theme, setTheme }}>
            {children}
          </ThemeContext.Provider>
        </UserContext.Provider>
      );
    }
    
  • Confirm that user-only consumers no longer re-render on theme changes after the split.

    function UserBadge() {
      const { user } = useContext(UserContext)!;
      // Only re-renders when user changes — theme updates are ignored
      return <span>{user?.name}</span>;
    }
    
  • Let each context update independently so unrelated parts of the tree stay stable.

    function ThemeToggle() {
      const { theme, setTheme } = useContext(ThemeContext)!;
      // Changing theme does NOT re-render UserBadge above
      return <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>{theme}</button>;
    }
    
  • Split read and write into separate contexts so read-only consumers never re-render from setter changes.

    const UserStateContext = createContext<User | null>(null);    // just the value
    const UserDispatchContext = createContext<(u: User | null) => void>(() => {}); // just the setter
    
    // Components that only display user data never re-render when setUser reference changes
    function UserDisplay() {
      const user = useContext(UserStateContext);
      return <p>{user?.name}</p>;
    }
    
  • Subscribe only to the contexts a component needs by consuming each context separately.

    function AdminPanel() {
      const { user } = useContext(UserContext)!;   // user updates trigger this
      // ThemeContext NOT consumed here — theme changes are irrelevant
      return user?.role === "admin" ? <div>Admin tools</div> : null;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Context Performance
Chapter 27 · Page 192
Advanced

React Context Performance

(continued)

Memoizing Context Values

  • Wrap the context value object in useMemo so a new reference is only created when dependencies change.

    function UserProvider({ children }: { children: React.ReactNode }) {
      const [user, setUser] = useState<User | null>(null);
    
      const userValue = useMemo(
        () => ({ user, setUser }),
        [user] // only creates a new object when user changes
      );
    
      return (
        <UserContext.Provider value={userValue}>
          {children}
        </UserContext.Provider>
      );
    }
    
  • List every value used from the context object in the dependency array to avoid stale closures.

    const value = useMemo(
      () => ({ user, settings, updateUser }),
      [user, settings, updateUser] // include all three dependencies
    );
    
  • Combine useMemo with stable function references so functions don't invalidate the memo.

    const updateUser = useCallback((newUser: User) => {
      setUser(newUser);
    }, []); // stable reference — empty deps because setUser is always stable
    
    const value = useMemo(() => ({ user, updateUser }), [user, updateUser]);
    
  • Verify that memoization is working by checking that consumers don't re-render on unrelated parent renders.

    // Parent re-renders due to its own state, but context value stays the same
    // Consumers should NOT re-render — confirm in React DevTools Profiler
    function ParentWithOwnState() {
      const [tick, setTick] = useState(0); // this state is unrelated to context
      return <button onClick={() => setTick(t => t + 1)}>Tick: {tick}</button>;
    }
    
  • Avoid memoizing the initial value inline — always use useMemo inside the provider component.

    // Wrong: initial value object is only created once but is a constant, not reactive
    <MyContext.Provider value={{ user: null, setUser: () => {} }}>
    
    // Right: useMemo inside the provider component reacts to state changes
    const value = useMemo(() => ({ user, setUser }), [user]);
    <MyContext.Provider value={value}>
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Context Performance
Chapter 27 · Page 193
Advanced

React Context Performance

(continued)

Memoized Consumers

  • Wrap context consumer components in React.memo so they skip re-renders when props are unchanged.

    const UserDisplay = React.memo(function UserDisplay() {
      const { user } = useContext(UserContext)!;
      // Skips re-renders when parent state changes but context value is stable
      return <div>{user?.name}</div>;
    });
    
  • Combine React.memo with a memoized context value for the best re-render reduction.

    // Provider memoizes the value object
    const value = useMemo(() => ({ user, setUser }), [user]);
    
    // Consumer is wrapped in React.memo
    const UserBadge = React.memo(() => {
      const { user } = useContext(UserContext)!;
      return <span>{user?.name}</span>;
    });
    // Only re-renders when the user object actually changes
    
  • Understand that React.memo still re-renders when the context value itself changes.

    // React.memo protects against prop changes, not context changes
    // If useContext returns a new reference, the component WILL re-render
    // Combine with useMemo on the provider value to control this
    
  • Pass a custom comparison function to React.memo for fine-grained prop-level control.

    const UserDisplay = React.memo(
      ({ label }: { label: string }) => {
        const { user } = useContext(UserContext)!;
        return <p>{label}: {user?.name}</p>;
      },
      (prevProps, nextProps) => prevProps.label === nextProps.label // only re-render if label changes
    );
    
  • Extract the context-consuming logic into a child component so React.memo can protect the expensive UI.

    // Expensive chart component is memoized; only re-renders when chartData changes
    const Chart = React.memo(({ chartData }: { chartData: number[] }) => {
      return <canvas>{/* expensive render */}</canvas>;
    });
    
    function ChartContainer() {
      const { chartData } = useContext(DashboardContext)!;
      return <Chart chartData={chartData} />; // passes stable reference when memoized
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Context Performance
Chapter 27 · Page 194
Advanced

React Context Performance

(continued)

Callback Optimization

  • Wrap context callbacks in useCallback so their references stay stable across parent renders.

    function UserProvider({ children }: { children: React.ReactNode }) {
      const [user, setUser] = useState<User | null>(null);
    
      const updateUser = useCallback((newUser: User) => {
        setUser(newUser); // setUser is stable, so deps array is empty
      }, []);
    
      const value = useMemo(() => ({ user, updateUser }), [user, updateUser]);
    
      return (
        <UserContext.Provider value={value}>{children}</UserContext.Provider>
      );
    }
    
  • Understand that functions created inline on every render invalidate the memoized context object.

    // Bad: inline function creates new reference every render
    const value = useMemo(() => ({
      user,
      updateUser: (u: User) => setUser(u), // new reference each render
    }), [user]); // updateUser changes every render, memo is useless
    
    // Good: useCallback gives updateUser a stable reference
    const updateUser = useCallback((u: User) => setUser(u), []);
    const value = useMemo(() => ({ user, updateUser }), [user, updateUser]);
    
  • Use useReducer dispatch instead of multiple callbacks — dispatch is always stable, no useCallback needed.

    const [state, dispatch] = useReducer(userReducer, initialState);
    
    // dispatch never changes reference — safe to include in memoized context value
    const value = useMemo(() => ({ state, dispatch }), [state]);
    
  • Compose multiple stable callbacks into a single memoized actions object for cleaner context values.

    const actions = useMemo(() => ({
      login: (credentials: Credentials) => dispatch({ type: "LOGIN", payload: credentials }),
      logout: () => dispatch({ type: "LOGOUT" }),
      updateProfile: (data: Partial<User>) => dispatch({ type: "UPDATE", payload: data }),
    }), [dispatch]); // dispatch is stable, so actions object is stable
    
  • Prefer useReducer over useState in context providers to eliminate most useCallback needs.

    // useState approach: need useCallback for every mutator
    const [user, setUser] = useState(null);
    const updateName = useCallback((name) => setUser(u => ({ ...u!, name })), []);
    
    // useReducer approach: dispatch is always stable — no useCallback needed
    const [state, dispatch] = useReducer(reducer, { user: null });
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Context Performance
Chapter 27 · Page 195
Advanced

React Context Performance

(FAQ)

FAQ

Every component subscribed to a context re-renders whenever any value in that context changes, even if the component only uses an unrelated field. Split your context into separate providers per concern so updates to one domain don't trigger consumers of another.

Wrap the value object in useMemo with its dependencies: const value = useMemo(() => ({ user, setUser }), [user]). Without this, a new object reference is created on every parent render, causing all consumers to re-render even when the data hasn't changed.

Wrap a consumer in React.memo when it receives context values as props passed down from a parent rather than via useContext directly — this lets React bail out of re-rendering if the specific props it receives haven't changed. Components using useContext directly still re-render on context changes regardless of React.memo.

Functions defined inline in a component recreate on every render, making them new references that invalidate the memoized context object. Wrap each function with useCallback and declare its dependencies so the reference stays stable between renders.

There's no hard limit — providers are cheap and multiple providers composed in a tree is the recommended pattern. Combining unrelated state into one context trades boilerplate reduction for broader re-render scope, which is usually the wrong tradeoff for frequently-updating values.

Useful Cheatsheetsusefulcheatsheets.com
React Memo and Lazy Loading
Chapter 28 · Page 196
Advanced

React Memo and Lazy Loading

Optimize component rendering with React.memo, lazy loading, and code splitting strategies.

TL;DR

  1. 01Use React.memo to skip re-renders when props haven't changed.
  2. 02Use lazy loading to split code and load components on demand.
  3. 03Combine both for significant performance improvements.

Tips

  1. 01Unlike regular Suspense data-fetching failures, a failed dynamic import() throws a module load error — always pair React.lazy with an ErrorBoundary in production.

Warnings

  1. 01React.memo only does shallow comparison, so passing new objects or arrays as props defeats memoization — use useMemo or useCallback to keep props stable.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Memo and Lazy Loading
Chapter 28 · Page 197
Advanced

React Memo and Lazy Loading

(continued)

React.memo

  • Memoize a component to skip re-renders when props are the same.
    const UserCard = React.memo(({ user }) => (
      <div>{user.name}</div>
    ));
  • React.memo compares new and old props using shallow equality.
  • Only skip re-renders if props haven't changed.
    // Parent re-renders but UserCard doesn't if user prop is the same
    const Parent = () => {
      const [count, setCount] = useState(0);
      const user = useMemo(() => ({ id: 1, name: "Alice" }), []);
      
      return (
        <>
          <UserCard user={user} />
          <button onClick={() => setCount(count + 1)}>
            Count: {count}
          </button>
        </>
      );
    };
  • Use custom comparison for complex props.
    const UserCard = React.memo(
      ({ user }) => <div>{user.name}</div>,
      (prevProps, nextProps) => {
        return prevProps.user.id === nextProps.user.id;
      }
    );
  • Memoization has overhead, so only use when re-renders are expensive.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Memo and Lazy Loading
Chapter 28 · Page 198
Advanced

React Memo and Lazy Loading

(continued)

Lazy Loading

  • Use React.lazy to split code and load components on demand.

    import { lazy, Suspense } from "react";
    
    const HeavyComponent = lazy(() => import("./HeavyComponent"));
    
    export default function App() {
      return (
        <Suspense fallback={<p>Loading...</p>}>
          <HeavyComponent />
        </Suspense>
      );
    }
    
  • The component code is not included in the main bundle — it fetches only when first rendered.

  • Wrap lazy components in an ErrorBoundary to handle chunk load failures gracefully.

    <ErrorBoundary fallback={<p>Failed to load — check your connection.</p>}>
      <Suspense fallback={<p>Loading...</p>}>
        <HeavyComponent />
      </Suspense>
    </ErrorBoundary>
    
  • Load conditional UI only when needed — modals, settings panels, and charts are ideal candidates.

    const SettingsPanel = lazy(() => import("./SettingsPanel"));
    
    {showSettings && (
      <Suspense fallback={<p>Loading settings...</p>}>
        <SettingsPanel />
      </Suspense>
    )}
    
  • Works great for route-based code splitting in React Router or any SPA router.

Notes
Useful Cheatsheetsusefulcheatsheets.com
React Memo and Lazy Loading
Chapter 28 · Page 199
Advanced

React Memo and Lazy Loading

(continued)

Route-Based Code Splitting

  • Split code by route to load pages on demand in Next.js or React Router.
    import { lazy } from "react";
    
    const Dashboard = lazy(() => import("./pages/Dashboard"));
    const Settings = lazy(() => import("./pages/Settings"));
    const Profile = lazy(() => import("./pages/Profile"));
  • Each route is a separate chunk loaded when the user navigates.
  • Reduces the initial bundle size significantly.
    <Routes>
      <Route path="/dashboard" element={<Dashboard />} />
      <Route path="/settings" element={<Settings />} />
      <Route path="/profile" element={<Profile />} />
    </Routes>
  • Most effective optimization for large applications.
  • Combine with Suspense for loading states while pages load.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Memo and Lazy Loading
Chapter 28 · Page 200
Advanced

React Memo and Lazy Loading

(continued)

Component Splitting

  • Split large components into smaller memoized pieces.
    const Header = React.memo(() => <header>...</header>);
    const Content = React.memo(({ data }) => <main>{data}</main>);
    const Footer = React.memo(() => <footer>...</footer>);
    
    export default function Page({ data }) {
      return (
        <>
          <Header />
          <Content data={data} />
          <Footer />
        </>
      );
    }
  • Memoized children don't re-render when parent re-renders.
  • Only effective if children receive stable props.
  • Combine with useCallback to keep function props stable.
    const handleClick = useCallback(() => {
      // Handle click
    }, []);
    
    <Button onClick={handleClick} />
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Memo and Lazy Loading
Chapter 28 · Page 201
Advanced

React Memo and Lazy Loading

(continued)

Performance Monitoring

  • Use Profiler API to measure component rendering time.
    import { Profiler } from "react";
    
    <Profiler id="dashboard" onRender={onRender}>
      <Dashboard />
    </Profiler>
    
    function onRender(id, phase, actualDuration) {
      console.log(`${id} (${phase}) took ${actualDuration}ms`);
    }
  • Use React DevTools Profiler to identify slow components.
  • Measure before and after optimization to confirm improvements.
  • Only optimize the slow components, not everything.
    // Profile to find slow components first
    // Then apply memo, lazy, or splitting strategically
  • Avoid premature optimization — measure real performance first.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Memo and Lazy Loading
Chapter 28 · Page 202
Advanced

React Memo and Lazy Loading

(FAQ)

FAQ

Use React.memo to prevent a component from re-rendering when its props haven't changed. Use useMemo inside a component to memoize expensive computed values. They solve different problems and are often used together.

Wrap your import with React.lazy(() => import('./MyComponent')) and render it inside a Suspense boundary with a fallback UI. The component's code is only fetched when it's first rendered.

No — children is a new object reference on every render, so React.memo will always re-render if you pass JSX as children. Lift the children out or restructure your component tree to avoid this.

Route-based splitting lazy-loads entire page-level components when a route is first visited, which is the highest-impact change for initial load time. Component splitting targets large UI pieces (modals, charts) that aren't visible on first paint.

Use the Coverage tab in Chrome DevTools to see how much JavaScript is unused on initial load, and check the Network tab to confirm chunks are loaded on demand. React DevTools Profiler shows which components re-render unnecessarily.

Preview: React Cheatsheets