R
Rishtaara
React: The Complete Guide
Lesson 13 of 28Article20 min

Portals, Suspense, Transitions, Forward Ref & HOC

ReactDOM.createPortal lets you render a modal or tooltip into document.body while keeping it in the same React component tree for events and state.

Portals — render outside the parent DOM tree

ReactDOM.createPortal lets you render a modal or tooltip into document.body while keeping it in the same React component tree for events and state.

Real-life example: A portal is a pop-up stall outside the main shop — separate location, still run by the same owner (your React app).

Modal with createPortal
import { createPortal } from "react-dom";

function Modal({ open, children, onClose }) {
  if (!open) return null;
  return createPortal(
    <div className="overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.body
  );
}

Suspense — loading boundaries

Suspense shows a fallback UI while child components load data or lazy-loaded code. Pair with React.lazy for code splitting.

Real-life example: Suspense is a "Please wait" sign at a restaurant while the kitchen (lazy component) prepares your order.

Suspense with lazy loading
import { Suspense, lazy } from "react";

const HeavyChart = lazy(() => import("./HeavyChart.jsx"));

function Dashboard() {
  return (
    <Suspense fallback={<p>Loading chart...</p>}>
      <HeavyChart />
    </Suspense>
  );
}

Transitions — useTransition

useTransition marks state updates as non-urgent so typing stays smooth while a heavy list re-filters.

Real-life example: useTransition is letting urgent guests (typing) through the door first while heavy luggage (big re-render) waits in the queue.

useTransition for search filter
import { useState, useTransition } from "react";

function SearchList({ items }) {
  const [query, setQuery] = useState("");
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    const value = e.target.value;
    setQuery(value);
    startTransition(() => {
      // heavy filter update marked as transition
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <span>Updating...</span>}
    </>
  );
}

Forward Ref — pass refs to child DOM nodes

forwardRef lets a parent get a ref to a child's DOM element — useful for focus, measurements, or third-party libraries.

Real-life example: forwardRef is like giving your manager a direct phone line to your desk — they reach you without going through the whole office chain.

forwardRef input
import { forwardRef, useRef } from "react";

const FancyInput = forwardRef(function FancyInput(props, ref) {
  return <input ref={ref} className="fancy" {...props} />;
});

function Form() {
  const inputRef = useRef(null);
  return (
    <>
      <FancyInput ref={inputRef} placeholder="Focus me" />
      <button onClick={() => inputRef.current?.focus()}>Focus</button>
    </>
  );
}

HOC — Higher-Order Components

A HOC is a function that takes a component and returns a new component with extra props or behavior. Hooks replaced many HOC use cases, but you will see withAuth(UserPanel) patterns in legacy code.

Real-life example: A HOC is a gift wrapper — same gift inside (component), extra ribbon and tag (added behavior) outside.

Prefer custom hooks over HOCs for new code unless a library requires HOCs.
Simple HOC pattern
function withLoading(Wrapped) {
  return function WithLoading(props) {
    if (props.loading) return <p>Loading...</p>;
    return <Wrapped {...props} />;
  };
}

const UserListWithLoading = withLoading(UserList);