How to Use the useLoaderData Hook in React Router: A Complete Guide

The useLoaderData hook in React Router retrieves the serialized return value of a route's loader function by accessing the router's internal loaderData map using the current route ID.

The useLoaderData hook is a fundamental data-fetching primitive in React Router's modern data API, available in both Framework and Data Router modes. As implemented in the remix-run/react-router repository, this hook enables any component within a route hierarchy to access loader results without prop drilling or additional context wiring.

What is useLoaderData in React Router?

useLoaderData is a React hook designed specifically for React Router v6.4+ data routers. It provides type-safe access to the data returned by a route's loader function, which runs before the route component renders. The hook automatically associates itself with the nearest route in the component tree that defines a loader or clientLoader, ensuring that each component receives exactly the data intended for its specific route segment.

How useLoaderData Works Under the Hood

The implementation of useLoaderData resides in packages/react-router/lib/hooks.tsx. The hook follows a three-step resolution process to retrieve loader data from the router's global state.

Accessing the Router State

First, useLoaderData invokes useDataRouterState(DataRouterStateHook.UseLoaderData) to access the global DataRouterStateContext. This context contains the router's current state object, including the loaderData map that stores serialized results for every route that has executed its loader. If this hook is called outside of a <RouterProvider>, useDataRouterState throws an invariant error instructing developers to use a proper data router.

Resolving the Current Route ID

Next, the hook calls useCurrentRouteId(DataRouterStateHook.UseLoaderData) to determine the ID of the route currently rendering the component. React Router maintains a mapping between route definitions and their rendered instances, allowing useLoaderData to identify exactly which entry in the global loaderData map corresponds to the calling component.

Retrieving the Loader Result

Finally, useLoaderData returns state.loaderData[routeId] cast to SerializeFrom<T>, where T is the generic type parameter inferred from the loader function. The SerializeFrom type utility ensures that the returned data conforms to the serialized form of the loader's return value, handling type transformations for JSON-serializable data.

useLoaderData TypeScript and Generic Support

One of the most powerful features of useLoaderData is its generic type parameter T. When you invoke useLoaderData<typeof loader>(), TypeScript automatically infers the return type of your loader function and applies the SerializeFrom transformation. This provides end-to-end type safety from your server-side or client-side loader through to your React component, catching type mismatches at compile time rather than runtime.

Practical useLoaderData Examples

The following examples demonstrate common patterns for accessing loader data in React Router applications.

Basic Loader Data Access

This example shows the standard pattern for defining a loader and accessing its result in the route component:

// loader that fetches invoices from a mock API
export async function loader() {
  return await fakeDb.invoices.findAll();
}

// Component that reads the loader data
export default function Invoices() {
  // TypeScript infers the shape of invoices from the loader
  const invoices = useLoaderData<typeof loader>();
  return (
    <ul>
      {invoices.map((inv) => (
        <li key={inv.id}>{inv.title}</li>
      ))}
    </ul>
  );
}

Nested Route Data Fetching

In nested route hierarchies, each route can define its own loader, and useLoaderData automatically returns the data for the specific route where the component is rendered:

// Parent route loader
export async function loader() {
  return await fetchDashboardData();
}

// Child route loader
export async function loader({ params }) {
  return await fetchUser(params.userId);
}

// Child component accessing its own loader data
export default function UserProfile() {
  const user = useLoaderData<typeof loader>();
  return (
    <section>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </section>
  );
}

Accessing Parent Route Data with useMatches

When you need to access loader data from a parent or ancestor route rather than the current route, use the useMatches hook to traverse the route hierarchy:

export default function Dashboard() {
  const matches = useMatches();               // contains all UI matches
  const rootData = matches.find(m => m.id === "root")?.data; // data from root loader
  return <div>{JSON.stringify(rootData)}</div>;
}

Common useLoaderData Errors and Safety Checks

The useLoaderData hook includes strict runtime checks to prevent misuse. If you attempt to call useLoaderData outside of a <RouterProvider> component tree, the underlying useDataRouterState function throws an invariant error with a clear message instructing you to use a proper data router. This safety mechanism ensures that loader data is only accessed within the context of an active data router that can actually provide the loaderData map and route resolution logic.

Summary

  • useLoaderData retrieves serialized loader results from the nearest route in React Router's data router architecture.
  • The hook is implemented in packages/react-router/lib/hooks.tsx and relies on useDataRouterState and useCurrentRouteId to resolve the correct data.
  • It returns SerializeFrom<T> where T is the loader's return type, providing full TypeScript inference when called as useLoaderData<typeof loader>().
  • The hook throws a runtime error if used outside of a <RouterProvider> context.
  • For accessing ancestor route data, use useMatches instead of useLoaderData.

Frequently Asked Questions

What happens if I use useLoaderData outside of a RouterProvider?

If you call useLoaderData outside of a <RouterProvider> component tree, the hook throws a runtime invariant error. The underlying useDataRouterState function checks for the existence of DataRouterStateContext and fails with a clear message instructing you to use a proper data router. This ensures loader data can only be accessed within an active data router context.

How does useLoaderData know which route's data to return?

useLoaderData determines the correct route by calling useCurrentRouteId(DataRouterStateHook.UseLoaderData), which identifies the nearest route in the component tree that defines a loader or clientLoader. The hook then indexes into the global loaderData map using this route ID, ensuring you always receive the data specific to the route rendering your component.

Can I use useLoaderData with clientLoader in React Router?

Yes, useLoaderData works seamlessly with clientLoader in React Router's framework mode. The hook retrieves data from the loaderData map regardless of whether the data was fetched by a server loader or a client-side clientLoader. TypeScript inference works identically for both patterns when you pass typeof loader or typeof clientLoader as the generic parameter.

What is the difference between useLoaderData and useMatches?

useLoaderData returns the loader data for the specific route where the component is rendered, automatically scoping to the nearest route with a loader. useMatches returns an array of all active route matches in the hierarchy, allowing you to access data from parent or ancestor routes by searching the matches array. Use useLoaderData for direct route data access and useMatches when you need cross-route data visibility.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →