React `startTransition` Without `useTransition`: The Standalone API Teams Keep Overlooking in Concurrent Mode A developer-authored technical writeup argues that React teams overlook the standalone `startTransition` export, which schedules low-priority concurrent-mode updates without requiring component scope. Unlike the `useTransition` hook, which throws an "Invalid hook call" outside components and returns an `isPending` flag, the standalone function can be called from store subscribers, module-level listeners, and other synchronous contexts, though it provides no pending-state signal. startTransition Without useTransition : The Standalone API Teams Keep Overlooking in Concurrent Mode This article was written with the assistance of AI, under human supervision and review. Most React concurrent-mode problems stem from teams treating useTransition as the only entry point. The pattern developers overlook is React's standalone startTransition export, a function that schedules low-priority updates without requiring component scope. This distinction is critical. When event handlers live in third-party stores, synchronous callbacks fire outside the React tree, or non-component modules need to trigger deferred updates, useTransition becomes unavailable. Teams either abandon concurrent scheduling or force awkward component boundaries to access the hook. The standalone API eliminates both compromises. The failure mode here is subtle but expensive. Reach for useTransition in a click handler inside a Zustand store and React throws "Invalid hook call". Move that handler into a component wrapper to fix the error and the store logic fragments across boundaries. Use useState alone to avoid the hook restriction and every state update blocks rendering, reintroducing the jank concurrent mode was meant to solve. The standalone startTransition function imported directly from react schedules transitions anywhere synchronous JavaScript runs, no component context required. Before: useTransition hook throws error when called outside component scope The solution is mechanical. Import startTransition as a named export and wrap state updates that should not block urgent work. The function signature matches useTransition 's callback argument but stands alone. No hooks, no component tree, no special context. The React scheduler marks those updates as deferred, processes urgent updates first, then commits transitions when the main thread quiets. Production codebases gain concurrent benefits in layers React components never touch. After: startTransition schedules transitions without hooks from any synchronous context isPending flag, unlike The standalone startTransition function and the useTransition hook solve the same scheduling problem but serve different contexts. Both mark state updates as low priority so React can process urgent work first. The difference is in return values and caller requirements. useTransition is a hook that returns a tuple: the startTransition callback and an isPending boolean. The hook requires component scope and obeys React's rules of hooks. The standalone function is a plain JavaScript export that accepts a callback and returns nothing. Developers call it from any synchronous context, no component, no custom hook wrapper, no restrictions beyond standard function semantics. Hook and standalone API diverge after invocation context check The hook's isPending flag drives loading indicators tied to component state. When a transition starts, React sets the flag to true. The component re-renders with a spinner or skeleton. When the transition commits, the flag flips to false and the final content appears. The standalone function provides no such signal. Call startTransition and the update queues silently. The only observable effect is that urgent updates, typing in an input, clicking a button: render immediately while the transition waits. Teams that need explicit pending feedback must track it separately or combine the standalone API with hook-based indicators in components that do have access to useTransition . The implication here is architectural. Use the hook when transitions originate inside React components and the UI must reflect pending state. Use the standalone function when transitions start outside components, in store subscribers, module-level event listeners, or synchronous callbacks passed to libraries that do not accept hooks. The choice is not about capability but about caller location and whether the pending signal matters to the user experience. Both APIs schedule identically under the hood. React's reconciler treats updates wrapped in either form as interruptible, yielding to higher-priority work until the browser idles. The standalone API becomes necessary when code that triggers state updates lives outside React's component tree. Third-party state management libraries like Zustand or Jotai publish updates from store modules. These modules are plain JavaScript, no JSX, no component lifecycle, no access to hooks. When a store action dispatches a large state change that should not block the UI, wrapping that dispatch in useTransition fails immediately with React's "Invalid hook call" error. The store has no component context. The solution is to import startTransition and call it directly in the store action. Event handlers attached outside React present the same constraint. A legacy codebase might attach click listeners to the DOM with addEventListener in a module that predates the React refactor. That listener needs to update React state without blocking urgent input. Refactoring the listener into a component just to access useTransition is mechanical busywork that pollutes component boundaries. The standalone function schedules the update from the listener without touching the component tree. Synchronous callbacks passed to non-React libraries hit the same wall. An animation library accepts an onComplete callback that fires when a transition finishes. That callback updates state to reflect the animation's end. The callback is a plain function reference, not a component render. Hooks do not work here. The standalone API does. Wrap the state update in startTransition and React defers it correctly despite the callback's non-React origin. The pattern generalizes: any synchronous JavaScript context that cannot call hooks but must schedule concurrent updates requires the standalone function. The alternative is forcing component wrappers around every non-React integration point, fragmenting logic and coupling concurrent scheduling to component structure. The standalone export breaks that coupling. Schedule transitions from any module that can import from react . No hooks, no components, no architectural compromises. A Zustand store managing a large dataset demonstrates the standalone API's mechanics. The store exports an action that filters thousands of items based on user input. Filtering blocks the main thread for 100ms. Users type into a search box and the UI freezes until the filter completes. The fix is wrapping the filter dispatch in startTransition so typing stays responsive while React processes the filter result when idle. js import { create } from 'zustand'; import { startTransition } from 'react'; interface Item { id: string; name: string; category: string; } interface StoreState { items: Item ; filtered: Item ; filter: string; setFilter: term: string = void; } const useStore = create