CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-router-dom

Declarative routing for React web applications

Pending
Overview
Eval results
Files

data-loading-hooks.mddocs/

Data Loading Hooks

Hooks for accessing route data including loader data, action data, and data fetching utilities with support for revalidation and optimistic updates.

Capabilities

useLoaderData

Returns data from the current route's loader function.

/**
 * Hook that returns data from the current route's loader
 * @returns Data returned by the route's loader function
 */
function useLoaderData<T = any>(): T;

Usage Examples:

import { useLoaderData } from "react-router-dom";

// Route with loader
<Route 
  path="/users/:id" 
  element={<UserProfile />}
  loader={async ({ params }) => {
    const response = await fetch(`/api/users/${params.id}`);
    return response.json();
  }}
/>

// Component using loader data
function UserProfile() {
  const user = useLoaderData<User>();
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <p>Joined: {user.joinDate}</p>
    </div>
  );
}

interface User {
  id: string;
  name: string;
  email: string;
  joinDate: string;
}

useActionData

Returns data from the most recent action submission.

/**
 * Hook that returns data from the most recent action submission
 * @returns Data returned by the route's action function, or undefined
 */
function useActionData<T = any>(): T | undefined;

Usage Examples:

import { useActionData, Form } from "react-router-dom";

// Route with action
<Route 
  path="/contact" 
  element={<ContactForm />}
  action={async ({ request }) => {
    const formData = await request.formData();
    try {
      await submitContact(formData);
      return { success: true, message: "Message sent!" };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }}
/>

// Component using action data
function ContactForm() {
  const actionData = useActionData<ActionData>();
  
  return (
    <div>
      <Form method="post">
        <input name="name" type="text" required />
        <input name="email" type="email" required />
        <textarea name="message" required></textarea>
        <button type="submit">Send</button>
      </Form>
      
      {actionData?.success && (
        <p className="success">{actionData.message}</p>
      )}
      
      {actionData?.error && (
        <p className="error">{actionData.error}</p>
      )}
    </div>
  );
}

interface ActionData {
  success: boolean;
  message?: string;
  error?: string;
}

useRouteLoaderData

Returns loader data from a specific route by ID.

/**
 * Hook that returns loader data from a specific route by ID
 * @param routeId - ID of the route to get data from
 * @returns Loader data from the specified route, or undefined
 */
function useRouteLoaderData<T = any>(routeId: string): T | undefined;

Usage Examples:

import { useRouteLoaderData } from "react-router-dom";

// Route configuration with IDs
const routes = [
  {
    id: "root",
    path: "/",
    element: <Root />,
    loader: rootLoader,
    children: [
      {
        id: "dashboard",
        path: "dashboard",
        element: <Dashboard />,
        loader: dashboardLoader,
        children: [
          {
            path: "profile",
            element: <Profile />,
          },
        ],
      },
    ],
  },
];

// Access parent route data from child component
function Profile() {
  const rootData = useRouteLoaderData<RootData>("root");
  const dashboardData = useRouteLoaderData<DashboardData>("dashboard");
  
  return (
    <div>
      <h1>Profile for {rootData?.user.name}</h1>
      <p>Dashboard stats: {dashboardData?.stats}</p>
    </div>
  );
}

useFetcher

Returns a fetcher object for loading data and submitting forms without navigation.

/**
 * Hook that returns a fetcher for data loading and form submission
 * @returns Fetcher object with load, submit, and Form components
 */
function useFetcher<T = any>(): FetcherWithComponents<T>;

interface FetcherWithComponents<T> {
  /** Form component bound to this fetcher */
  Form: React.ComponentType<FetcherFormProps>;
  /** Function to submit form data */
  submit: FetcherSubmitFunction;
  /** Function to load data from a URL */
  load: (href: string, opts?: { unstable_flushSync?: boolean }) => void;
  /** Data returned from the fetcher request */
  data: T;
  /** Current form data being submitted */
  formData?: FormData;
  /** Current JSON data being submitted */
  json?: unknown;  
  /** Current text data being submitted */
  text?: string;
  /** Current form action URL */
  formAction?: string;
  /** Current form method */
  formMethod?: string;
  /** Current form encoding type */
  formEncType?: string;
  /** Current fetcher state */
  state: "idle" | "loading" | "submitting";
}

interface FetcherFormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, 'action' | 'method'> {
  action?: string;
  method?: "get" | "post" | "put" | "patch" | "delete";
  encType?: "application/x-www-form-urlencoded" | "multipart/form-data";
  replace?: boolean;
  preventScrollReset?: boolean;
}

type FetcherSubmitFunction = (
  target: SubmitTarget,
  options?: FetcherSubmitOptions
) => void;

type SubmitTarget = 
  | HTMLFormElement 
  | FormData 
  | URLSearchParams 
  | { [name: string]: string | File | (string | File)[] };

interface FetcherSubmitOptions {
  action?: string;
  method?: "get" | "post" | "put" | "patch" | "delete";
  encType?: "application/x-www-form-urlencoded" | "multipart/form-data";
  replace?: boolean;
  preventScrollReset?: boolean;
  unstable_flushSync?: boolean;
}

Usage Examples:

import { useFetcher } from "react-router-dom";

// Load data without navigation
function ProductQuickView({ productId }) {
  const fetcher = useFetcher<Product>();
  
  const loadProduct = () => {
    fetcher.load(`/api/products/${productId}`);
  };
  
  return (
    <div>
      <button onClick={loadProduct}>Load Product</button>
      
      {fetcher.state === "loading" && <p>Loading...</p>}
      
      {fetcher.data && (
        <div>
          <h3>{fetcher.data.name}</h3>
          <p>${fetcher.data.price}</p>
        </div>
      )}
    </div>
  );
}

// Submit form without navigation
function AddToCart({ productId }) {
  const fetcher = useFetcher();
  
  const isAdding = fetcher.state === "submitting";
  
  return (
    <fetcher.Form method="post" action="/api/cart">
      <input type="hidden" name="productId" value={productId} />
      <input type="number" name="quantity" defaultValue="1" />
      <button type="submit" disabled={isAdding}>
        {isAdding ? "Adding..." : "Add to Cart"}
      </button>
    </fetcher.Form>
  );
}

// Programmatic submission
function QuickActions() {
  const fetcher = useFetcher();
  
  const likePost = (postId: string) => {
    fetcher.submit(
      { postId, action: "like" },
      { method: "post", action: "/api/posts/like" }
    );
  };
  
  return (
    <button onClick={() => likePost("123")}>
      Like Post
    </button>
  );
}

useFetchers

Returns an array of all active fetchers.

/**
 * Hook that returns all active fetchers in the application
 * @returns Array of all active fetcher objects
 */
function useFetchers(): Fetcher[];

interface Fetcher<T = any> {
  data: T;
  formData?: FormData;
  json?: unknown;
  text?: string;
  formAction?: string;
  formMethod?: string;
  formEncType?: string;
  state: "idle" | "loading" | "submitting";
  key: string;
}

Usage Example:

import { useFetchers } from "react-router-dom";

function GlobalLoadingIndicator() {
  const fetchers = useFetchers();
  
  const activeFetchers = fetchers.filter(
    fetcher => fetcher.state === "loading" || fetcher.state === "submitting"
  );
  
  if (activeFetchers.length === 0) return null;
  
  return (
    <div className="loading-indicator">
      {activeFetchers.length} active requests...
    </div>
  );
}

useRevalidator

Returns an object for triggering data revalidation.

/**
 * Hook that returns revalidation utilities
 * @returns Revalidator object with state and revalidate function
 */
function useRevalidator(): Revalidator;

interface Revalidator {
  /** Trigger revalidation of all route loaders */
  revalidate(): void;
  /** Current revalidation state */
  state: "idle" | "loading";
}

Usage Example:

import { useRevalidator } from "react-router-dom";

function RefreshButton() {
  const revalidator = useRevalidator();
  
  return (
    <button 
      onClick={() => revalidator.revalidate()}
      disabled={revalidator.state === "loading"}
    >
      {revalidator.state === "loading" ? "Refreshing..." : "Refresh Data"}
    </button>
  );
}

useNavigation

Returns the current navigation state.

/**
 * Hook that returns information about the current navigation
 * @returns Navigation object with current navigation state
 */
function useNavigation(): Navigation;

interface Navigation {
  /** Current navigation state */
  state: "idle" | "loading" | "submitting";
  /** Location being navigated to */
  location?: Location;
  /** Form data being submitted */
  formData?: FormData;
  /** JSON data being submitted */
  json?: unknown;
  /** Text data being submitted */
  text?: string;
  /** Form action URL */
  formAction?: string;
  /** Form method */
  formMethod?: string;
  /** Form encoding type */
  formEncType?: string;
}

Usage Example:

import { useNavigation } from "react-router-dom";

function GlobalLoadingBar() {
  const navigation = useNavigation();
  
  if (navigation.state === "idle") return null;
  
  return (
    <div className="loading-bar">
      {navigation.state === "loading" && "Loading..."}
      {navigation.state === "submitting" && "Saving..."}
    </div>
  );
}

useAsyncValue

Hook for accessing resolved values from Await components in Suspense boundaries.

/**
 * Hook that returns the resolved value from an Await component
 * @returns The resolved value from the nearest Await component
 */
function useAsyncValue<T = any>(): T;

Usage Examples:

import { useAsyncValue, Await } from "react-router-dom";

// Component that displays resolved async data
function UserDetails() {
  const user = useAsyncValue<User>();
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <p>Posts: {user.postCount}</p>
    </div>
  );
}

// Usage with Await component
function UserProfile() {
  const data = useLoaderData<{ userPromise: Promise<User> }>();
  
  return (
    <div>
      <h1>User Profile</h1>
      <Suspense fallback={<div>Loading user...</div>}>
        <Await resolve={data.userPromise}>
          <UserDetails />
        </Await>
      </Suspense>
    </div>
  );
}

useAsyncError

Hook for accessing errors from Await components in error boundaries.

/**
 * Hook that returns the error from an Await component
 * @returns The error thrown by the nearest Await component
 */
function useAsyncError(): unknown;

Usage Examples:

import { useAsyncError, Await } from "react-router-dom";

// Error boundary component for async errors
function AsyncErrorBoundary() {
  const error = useAsyncError();
  
  return (
    <div className="error">
      <h2>Failed to load data</h2>
      <p>{error instanceof Error ? error.message : "Unknown error occurred"}</p>
      <button onClick={() => window.location.reload()}>
        Try Again
      </button>
    </div>
  );
}

// Usage with Await component and error handling
function UserProfileWithError() {
  const data = useLoaderData<{ userPromise: Promise<User> }>();
  
  return (
    <div>
      <h1>User Profile</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <Await 
          resolve={data.userPromise}
          errorElement={<AsyncErrorBoundary />}
        >
          <UserDetails />
        </Await>
      </Suspense>
    </div>
  );
}

// Loader that returns deferred data
export const userLoader = async ({ params }) => {
  // Return immediately with a promise for deferred loading
  const userPromise = fetch(`/api/users/${params.id}`)
    .then(res => {
      if (!res.ok) throw new Error("Failed to load user");
      return res.json();
    });
  
  return defer({ userPromise });
};

Navigation Blocking

Hooks for blocking navigation conditionally.

/**
 * Hook that blocks navigation based on a condition
 * @param when - Condition to block navigation
 * @param message - Message to show when blocking (legacy browsers)
 * @returns Blocker object with current blocking state
 */
function useBlocker(when: boolean | BlockerFunction): Blocker;

/**
 * Hook that prompts user before navigation (unstable)
 * @param when - Condition to show prompt
 * @param message - Message to show in prompt
 */
function unstable_usePrompt(when: boolean, message?: string): void;

type BlockerFunction = (args: BlockerFunctionArgs) => boolean;

interface BlockerFunctionArgs {
  currentLocation: Location;
  nextLocation: Location;
  historyAction: NavigationType;
}

interface Blocker {
  state: "unblocked" | "blocked" | "proceeding";
  proceed(): void;
  reset(): void;
  location?: Location;
}

Usage Examples:

import { useBlocker, unstable_usePrompt } from "react-router-dom";

function UnsavedChangesForm() {
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
  
  // Block navigation when there are unsaved changes
  const blocker = useBlocker(hasUnsavedChanges);
  
  // Legacy browser support
  unstable_usePrompt(
    hasUnsavedChanges, 
    "You have unsaved changes. Are you sure you want to leave?"
  );
  
  return (
    <div>
      <form onChange={() => setHasUnsavedChanges(true)}>
        <input name="title" />
        <textarea name="content" />
        <button type="submit">Save</button>
      </form>
      
      {blocker.state === "blocked" && (
        <div className="modal">
          <p>You have unsaved changes. Are you sure you want to leave?</p>
          <button onClick={blocker.proceed}>Leave</button>
          <button onClick={blocker.reset}>Stay</button>
        </div>
      )}
    </div>
  );
}

Install with Tessl CLI

npx tessl i tessl/npm-react-router-dom

docs

data-loading-hooks.md

index.md

navigation-components.md

navigation-hooks.md

route-configuration.md

router-components.md

router-creation.md

server-side-rendering.md

utilities.md

tile.json