Skip to main content

useRouteLoaderData

Returns the loader data for a given route by route ID.
This hook only works in Data and Framework modes.

Signature

function useRouteLoaderData<T = any>(
  routeId: string
): SerializeFrom<T> | undefined

Parameters

routeId
string
required
The ID of the route to return loader data from. In Framework mode, route IDs are the path of the route file relative to the app folder without the extension.

Returns

data
SerializeFrom<T> | undefined
The data returned from the specified route’s loader function, or undefined if not found.

Usage

Basic usage

Access parent route data from a child route:
// app/routes/dashboard.tsx
export async function loader() {
  const user = await getUser();
  return { user };
}

export default function Dashboard() {
  return <Outlet />;
}

// app/routes/dashboard.settings.tsx
import { useRouteLoaderData } from "react-router";

export default function Settings() {
  const data = useRouteLoaderData("routes/dashboard");
  
  return <h1>Settings for {data.user.name}</h1>;
}

Framework mode route IDs

In Framework mode, route IDs are automatically generated from file paths:
Route FilenameRoute ID
app/root.tsx"root"
app/routes/teams.tsx"routes/teams"
app/routes/teams.$id.tsx"routes/teams.$id"
app/whatever/teams.$id.tsx"whatever/teams.$id"
// Access root loader data from anywhere
const rootData = useRouteLoaderData("root");

Custom route IDs

You can specify custom route IDs in routes.ts:
// app/routes.ts
import { route } from "@react-router/dev/routes";

export default [
  route("/", "containers/app.tsx", { id: "app" }),
];

// In any component
const appData = useRouteLoaderData("app");

Access user data globally

Common pattern for accessing authenticated user data:
// app/root.tsx
export async function loader({ request }) {
  const user = await authenticator.isAuthenticated(request);
  return { user };
}

// Any nested route
import { useRouteLoaderData } from "react-router";

function UserMenu() {
  const data = useRouteLoaderData("root");
  
  if (!data?.user) {
    return <LoginButton />;
  }
  
  return (
    <div>
      <img src={data.user.avatar} />
      <span>{data.user.name}</span>
    </div>
  );
}

Type-safe wrapper

Create a custom hook for type safety:
// app/routes/dashboard.tsx
import type { loader } from "./+types.dashboard";

export { loader };

// utils/hooks.ts
import { useRouteLoaderData } from "react-router";
import type { loader as dashboardLoader } from "~/routes/dashboard";

export function useDashboard() {
  const data = useRouteLoaderData<typeof dashboardLoader>(
    "routes/dashboard"
  );
  
  if (!data) {
    throw new Error("Must be used within dashboard routes");
  }
  
  return data;
}

// Any child route
import { useDashboard } from "~/utils/hooks";

export default function DashboardSettings() {
  const { user, preferences } = useDashboard();
  return <SettingsForm user={user} preferences={preferences} />;
}

Access layout data

// app/routes/_auth.tsx (layout route)
export async function loader() {
  const permissions = await getPermissions();
  return { permissions };
}

export default function AuthLayout() {
  return <Outlet />;
}

// app/routes/_auth.admin.tsx
import { useRouteLoaderData } from "react-router";

export default function Admin() {
  const data = useRouteLoaderData("routes/_auth");
  
  if (!data?.permissions.isAdmin) {
    return <Forbidden />;
  }
  
  return <AdminPanel />;
}

Share data between sibling routes

// Parent route loads shared data
// app/routes/products.tsx
export async function loader() {
  const categories = await db.categories.findAll();
  return { categories };
}

export default function ProductsLayout() {
  return <Outlet />;
}

// Child route 1: app/routes/products._index.tsx
import { useRouteLoaderData } from "react-router";

export default function ProductsList() {
  const data = useRouteLoaderData("routes/products");
  return <CategoryFilter categories={data.categories} />;
}

// Child route 2: app/routes/products.new.tsx
import { useRouteLoaderData } from "react-router";

export default function NewProduct() {
  const data = useRouteLoaderData("routes/products");
  return <CategorySelect categories={data.categories} />;
}

Common Patterns

Check if data exists

const data = useRouteLoaderData("routes/dashboard");

if (!data) {
  // Route hasn't loaded yet or doesn't exist
  return null;
}

// Safe to use data
return <div>{data.user.name}</div>;
import { useMatches, useRouteLoaderData } from "react-router";

function Breadcrumbs() {
  const matches = useMatches();
  
  return (
    <nav>
      {matches.map((match) => {
        const data = useRouteLoaderData(match.id);
        const crumb = data?.breadcrumb;
        
        if (!crumb) return null;
        
        return (
          <Link key={match.id} to={match.pathname}>
            {crumb}
          </Link>
        );
      })}
    </nav>
  );
}

// In route loaders
export async function loader({ params }) {
  const project = await getProject(params.id);
  return {
    project,
    breadcrumb: project.name,
  };
}

Conditional rendering

function Header() {
  const rootData = useRouteLoaderData("root");
  const dashboardData = useRouteLoaderData("routes/dashboard");
  
  // Show different header based on available data
  if (dashboardData) {
    return <DashboardHeader data={dashboardData} />;
  }
  
  return <DefaultHeader user={rootData?.user} />;
}

Comparison with useLoaderData

FeatureuseLoaderDatauseRouteLoaderData
ScopeCurrent route onlyAny route by ID
ParametersNoneRoute ID required
Return typeAlways definedMay be undefined
Use caseAccess own dataAccess parent/other route data

Type Safety

With TypeScript (Framework mode)

import type { loader as rootLoader } from "~/root";

function Component() {
  const data = useRouteLoaderData<typeof rootLoader>("root");
  
  // TypeScript knows the shape, but it's optional
  if (data) {
    data.user.name;  // Typed correctly
  }
}

With TypeScript (Data mode)

interface RootLoaderData {
  user: User | null;
  env: Env;
}

function Component() {
  const data = useRouteLoaderData<RootLoaderData>("root");
  
  if (data?.user) {
    console.log(data.user.name);
  }
}

Build docs developers (and LLMs) love