useTransition is a hook, so it runs only inside a component or a custom hook. React 7,897 also exports a plain startTransition that schedules a Transition the same way from anywhere: a data module, a WebSocket handler, a router, a listener attached outside React. It has no call site of its own to report from, so there is no isPending flag.
import { startTransition } from 'react';
let results = [];
const listeners = new Set();
export const subscribe = (fn) => (listeners.add(fn), () => listeners.delete(fn));
export const getResults = () => results;
const socket = new WebSocket('wss://feed.example.com/scores');
socket.addEventListener('message', (event) => {
startTransition(() => { // a push from the server is never urgent
results = JSON.parse(event.data);
listeners.forEach((fn) => fn()); // components re-render at low priority
});
});Components read this store through useSyncExternalStore (useSyncExternalStore). Because the notification happens inside startTransition, a burst of messages cannot lock up a user typing on the same page. Since React 19 the callback may be async, which is what makes Actions (Form Actions) possible; setters after the first await still need their own nested startTransition.