new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

React Router · Guides · all subjects

concurrency

28 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

React Router cancels previous loader requests on new navigation

When a user clicks a link while a previous navigation is still in progress, React Router cancels the loader fetch requests from the first navigation and only proceeds with the latest navigation's requests. This mirrors browser behavior where the most recent action is prioritized.

React Router cancels original form submission when new submission interrupts

If a form is submitted and another submission occurs before the first completes, React Router cancels the original fetch requests and waits for the latest submission to complete before triggering revalidation. This mirrors how browsers handle multiple form submissions.

useFetcher allows multiple concurrent requests unlike navigation

Unlike navigation and form submissions which are limited to one request at a time, useFetcher allows multiple requests to be in flight simultaneously.

React Router commits data immediately when one submission completes among concurrent submissions

When multiple form submissions are in progress and one completes, React Router updates the UI with that data immediately without waiting for the other submissions to finish. This keeps the UI responsive. As remaining submissions complete, the UI continues to update with the latest data.

React Router discards earlier revalidation data if later submission completes first

If a later form submission's revalidation completes before an earlier one, React Router cancels and discards the earlier submission's revalidation data. Only the most recent data is committed to the UI, since it was requested later and is more likely to reflect the newest state.

Stale data can rarely appear when interrupted requests reach server after new submission completes

In rare edge cases with unusual network conditions, a canceled request may still reach the server after a subsequent submission and its revalidation have completed. Browser cancellation only releases browser resources but does not prevent the request from reaching the server. Users may briefly see data that differs from the server state. Timestamps in form submissions with server-side staleness checks can mitigate this issue.

Revalidation occurs after form submissions

After a form submission, React Router automatically fetches fresh data from the loaders associated with the route. This process is called revalidation.

useFetcher with Form example for rapid input-triggered requests

In a city search combobox component, keystrokes can trigger rapid network requests. Using useFetcher.Form with onChange handlers on input fields automatically manages these concurrent requests, ensuring displayed results match the most recent query without manual race condition handling: ```tsx export async function loader({ request }) { const { searchParams } = new URL(request.url); const cities = await searchCities(searchParams.get("q")); return cities; } export function CitySearchCombobox() { const fetcher = useFetcher<typeof loader>(); return ( <fetcher.Form action="/city-search"> <Combobox aria-label="Cities"> <ComboboxInput name="q" onChange={(event) => fetcher.submit(event.target.form) } /> {fetcher.data ? ( <ComboboxPopover className="shadow-popup"> {fetcher.data.length > 0 ? ( <ComboboxList> {fetcher.data.map((city) => ( <ComboboxOption key={city.id} value={city.name} /> ))} </ComboboxList> ) : ( <span>No results found</span> )} </ComboboxPopover> ) : null} </Combobox> </fetcher.Form> ); } ```

React Router automatic race condition handling

React Router automatically handles the most common race conditions found in web user interfaces. It is impossible to eliminate every possible race condition in an application.

Browser-inspired network concurrency handling

React Router's handling of network concurrency is heavily inspired by browser behavior when processing documents. When a user clicks a link to a new document and then clicks a different link before the new page finishes loading, the browser cancels the first request and immediately processes the new navigation. The same behavior applies to form submissions: when a pending form submission is interrupted by a new one, the first is canceled and the new submission is immediately processed.

Interrupted navigations cancel in-flight requests

Like the browser, interrupted navigations with links and form submissions will cancel in-flight data requests and immediately process the new event.

Fetcher self-interruption behavior

Fetchers cannot interrupt other fetcher instances, but they can interrupt themselves. When a fetcher is interrupted by a new submission on the same fetcher instance, the first request is canceled and the new one is immediately processed.

Fetcher revalidation and stale request handling

After a fetcher's action request returns to the browser, a revalidation for all page data is sent. This means multiple revalidation requests can be in-flight at the same time. React Router commits all fresh revalidation responses and cancels any stale requests. A stale request is any request that started earlier than one that has returned.

Backend race conditions remain outside React Router scope

Since networks are unpredictable and the server still processes canceled requests, the backend may still experience race conditions and have potential data integrity issues. These risks are the same as using default browser behavior with plain HTML forms, which are considered low risk and outside the scope of React Router.

Type-ahead combobox race condition prevention

When building a type-ahead combobox, as the user types and sends new requests to the server with each character, it is important to not show results for a value that is no longer in the text field. When using a fetcher, this is automatically managed: calls to fetcher.submit will cancel pending requests on that fetcher automatically, ensuring results for a different input value are never shown to the user.

Type-ahead combobox implementation example

Route loader for city search: ```tsx export async function loader({ request }) { const { searchParams } = new URL(request.url); return searchCities(searchParams.get("q")); } ``` Component with automatic race condition handling: ```tsx export function CitySearchCombobox() { const fetcher = useFetcher(); return ( <fetcher.Form action="/city-search"> <Combobox aria-label="Cities"> <ComboboxInput name="q" onChange={(event) => fetcher.submit(event.target.form) } /> {fetcher.data ? ( <ComboboxPopover className="shadow-popup"> {fetcher.data.length > 0 ? ( <ComboboxList> {fetcher.data.map((city) => ( <ComboboxOption key={city.id} value={city.name} /> ))} </ComboboxList> ) : ( <span>No results found</span> )} </ComboboxPopover> ) : null} </Combobox> </fetcher.Form> ); } ```

React Transitions in React Router overview

React 18 introduced transitions to differentiate urgent from non-urgent UI updates. React 19 enhances this with Actions and support for async functions in Transitions, plus the useOptimistic hook for showing instant feedback during Transitions. React Router introduces a useTransitions flag to handle the tension between new React features (pending states, optimistic UI) and existing Router patterns.

React Router startTransition default behavior

Since React Router v7, all router state updates are wrapped in React.startTransition by default. In React Router 6.13.0, React.startTransition was introduced behind the future.v7_startTransition flag to make React Router more Suspense-friendly.

useSyncExternalStore and startTransition conflict

React.useSyncExternalStore updates cannot be Transitions. useSyncExternalStore forces a sync update, which means fallbacks can be shown in update transitions that would otherwise avoid showing the fallback. This is one reason to opt-out of using startTransition for router state updates.

Opt-out of Transitions with useTransitions=false

Set useTransitions={false} on router components to opt-out of wrapping internal state updates in startTransition. This is used for applications not 'Transition-friendly' due to useSyncExternalStore or other reasons. Syntax for Framework Mode: <HydratedRouter useTransitions={false} />. For Data Mode: <RouterProvider useTransitions={false} />. For Declarative Mode: <BrowserRouter useTransitions={false} />.

Opt-in to Transitions with useTransitions=true

Set useTransitions={true} (or just useTransitions) on router components to opt-in to enhanced Transition behavior with React 19. This requires React 19 because it needs access to React.useOptimistic. Syntax for Framework Mode: <HydratedRouter useTransitions />. For Data Mode: <RouterProvider useTransitions />. For Declarative Mode: <BrowserRouter useTransitions />.

Transition-enabled navigation with useTransitions=true

When useTransitions=true is enabled, all internal state updates are wrapped in React.startTransition. All <Link> and <Form> navigations are wrapped in React.startTransition using the promise returned by useNavigate/useSubmit so the Transition lasts for the entire navigation duration. useNavigate/useSubmit do not automatically wrap in React.startTransition, allowing opt-out by using them directly.

useOptimistic state surfacing with useTransitions=true

When useTransitions=true is enabled in Framework/Data modes, router state updates during navigation are surfaced via useOptimistic. State related to ongoing navigation and fetchers is surfaced: state.navigation (useNavigation), state.revalidation (useRevalidator), state.actionData (useActionData), and state.fetchers (useFetcher/useFetchers). State related to current location is not surfaced: state.location (useLocation), state.matches (useMatches), state.loaderData (useLoaderData), state.errors (useRouteError).

Automatic vs manual Transition wrapping in React Router

When useTransitions=true, <Link> and <Form> are automatically wrapped in async Transitions. For useNavigate, useSubmit, and fetcher methods, you must manually wrap in startTransition. Examples of automatically Transition-enabled: <Link to="/path" />, <Form method="post" action="/path" />. Examples of manually Transition-enabled: startTransition(() => navigate("/path")), startTransition(() => submit(data, { method: 'post', action: "/path" })), startTransition(() => fetcher.load("/path")), startTransition(() => fetcher.submit(data, { method: "post", action: "/path" })).

Promise return/await requirement in startTransition

When wrapping navigate or submit in startTransition, you must always return or await the promise so that the Transition encompasses the full duration of the navigation. If you forget to return or await the promise, the Transition will end prematurely and things won't work as expected. Correct: startTransition(() => navigate("/path")) or startTransition(async () => { await navigate("/path") }). Incorrect: startTransition(() => { navigate("/path") }) or startTransition(async () => { navigate("/path") }).

popstate navigation optimistic state bug

There is a bug with optimistic states during popstate (back) navigations. If reading the current route during a back navigation that suspends on uncached data, set the optimistic state before navigating back or defer the optimistic update in a timer or microtask.

Future default behavior for useTransitions

React Router v8 plans to make the opt-in useTransitions behavior (useTransitions=true) the default, but will likely retain the opt-out flag for use cases such as useSyncExternalStore.

Pre-rendering concurrency for parallel processing

By default, pages are pre-rendered one path at a time. You can enable concurrency to pre-render multiple paths in parallel to speed up build times. To specify concurrency, move the prerender config into a prerender.paths field and set prerender.concurrency to a number like 4.

Give your agent this brain