CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-remix-run--react

React DOM bindings for Remix web framework providing components, hooks, and utilities for full-stack React applications

Pending
Overview
Eval results
Files

data-loading-hooks.mddocs/

Data Loading Hooks

Hooks for accessing server-loaded data, handling form submissions, and managing client-server communication in Remix applications.

Capabilities

useLoaderData

Access data loaded by the current route's loader function.

/**
 * Returns data loaded by the current route's loader function
 * Data is automatically serialized and type-safe with proper TypeScript inference
 * @returns The data returned from the route's loader function
 */
function useLoaderData<T = unknown>(): SerializeFrom<T>;

Usage Examples:

import { useLoaderData } from "@remix-run/react";
import type { LoaderFunctionArgs } from "@remix-run/node";

// Loader function
export async function loader({ params }: LoaderFunctionArgs) {
  const user = await getUser(params.userId);
  const posts = await getUserPosts(params.userId);
  
  return {
    user,
    posts,
    lastLogin: new Date(),
  };
}

// Component using loader data
export default function UserProfile() {
  const { user, posts, lastLogin } = useLoaderData<typeof loader>();
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Last login: {new Date(lastLogin).toLocaleDateString()}</p>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

useActionData

Access data returned by the current route's action function after form submission.

/**
 * Returns data from the current route's action function
 * Only available after a form submission, returns undefined otherwise
 * @returns The data returned from the route's action function or undefined
 */
function useActionData<T = unknown>(): SerializeFrom<T> | undefined;

Usage Examples:

import { useActionData, Form } from "@remix-run/react";
import type { ActionFunctionArgs } from "@remix-run/node";

// Action function
export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  const email = formData.get("email") as string;
  
  try {
    await subscribeToNewsletter(email);
    return { success: true, message: "Successfully subscribed!" };
  } catch (error) {
    return { 
      success: false, 
      message: "Failed to subscribe",
      errors: { email: "Invalid email address" }
    };
  }
}

// Component using action data
export default function Newsletter() {
  const actionData = useActionData<typeof action>();
  
  return (
    <div>
      <Form method="post">
        <input 
          type="email" 
          name="email" 
          required
          className={actionData?.errors?.email ? "error" : ""}
        />
        <button type="submit">Subscribe</button>
        {actionData?.errors?.email && (
          <span className="error">{actionData.errors.email}</span>
        )}
      </Form>
      
      {actionData?.message && (
        <div className={actionData.success ? "success" : "error"}>
          {actionData.message}
        </div>
      )}
    </div>
  );
}

useRouteLoaderData

Access loader data from any route in the current route hierarchy by route ID.

/**
 * Returns loader data from a specific route by route ID
 * Useful for accessing parent route data or shared data across route boundaries
 * @param routeId - The ID of the route whose loader data to access
 * @returns The loader data from the specified route or undefined if not found
 */
function useRouteLoaderData<T = unknown>(routeId: string): SerializeFrom<T> | undefined;

Usage Examples:

import { useRouteLoaderData } from "@remix-run/react";

// In app/root.tsx
export async function loader() {
  return {
    user: await getCurrentUser(),
    environment: process.env.NODE_ENV,
  };
}

// In any child route component
export default function ChildRoute() {
  // Access root loader data using route ID
  const rootData = useRouteLoaderData<typeof loader>("root");
  
  if (!rootData?.user) {
    return <div>Please log in to continue</div>;
  }
  
  return (
    <div>
      <p>Welcome, {rootData.user.name}!</p>
      <p>Environment: {rootData.environment}</p>
    </div>
  );
}

// In a nested layout accessing parent data
export default function DashboardLayout() {
  // Access dashboard loader data from child routes
  const dashboardData = useRouteLoaderData("routes/dashboard");
  
  return (
    <div>
      <nav>Dashboard Navigation</nav>
      {dashboardData && <UserStats stats={dashboardData.stats} />}
      <Outlet />
    </div>
  );
}

useFetcher

Create a fetcher for loading data or submitting forms without causing navigation.

/**
 * Creates a fetcher for data loading and form submission without navigation
 * Provides a way to interact with loaders and actions programmatically
 * @param opts - Optional fetcher configuration
 * @returns Fetcher instance with form components and submission methods
 */
function useFetcher<T = unknown>(opts?: FetcherOptions): FetcherWithComponents<T>;

interface FetcherOptions {
  /** Key to identify this fetcher instance for reuse */
  key?: string;
}

interface FetcherWithComponents<T = unknown> {
  /** Current state of the fetcher */
  state: "idle" | "loading" | "submitting";
  /** Data returned from the last successful fetch */
  data: SerializeFrom<T> | undefined;
  /** Form data being submitted */
  formData: FormData | undefined;
  /** JSON data being submitted */
  json: any;
  /** Text data being submitted */
  text: string | undefined;
  /** HTTP method of the current/last submission */
  formMethod: string | undefined;
  /** Action URL of the current/last submission */
  formAction: string | undefined;
  /** Form component for this fetcher */
  Form: React.ComponentType<FormProps>;
  /** Submit function for programmatic submissions */
  submit: SubmitFunction;
  /** Load function for programmatic data loading */
  load: (href: string) => void;
}

Usage Examples:

import { useFetcher } from "@remix-run/react";

// Simple data fetching
function UserSearch() {
  const fetcher = useFetcher();
  
  const handleSearch = (query: string) => {
    fetcher.load(`/api/users/search?q=${encodeURIComponent(query)}`);
  };
  
  return (
    <div>
      <input 
        type="search" 
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="Search users..."
      />
      
      {fetcher.state === "loading" && <div>Searching...</div>}
      
      {fetcher.data && (
        <ul>
          {fetcher.data.users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

// Form submission without navigation
function QuickAddForm() {
  const fetcher = useFetcher();
  
  const isAdding = fetcher.state === "submitting";
  const isSuccess = fetcher.data?.success;
  
  return (
    <fetcher.Form method="post" action="/api/quick-add">
      <input name="title" placeholder="Quick add..." required />
      <button type="submit" disabled={isAdding}>
        {isAdding ? "Adding..." : "Add"}
      </button>
      
      {isSuccess && <div>Added successfully!</div>}
    </fetcher.Form>
  );
}

// Programmatic form submission
function LikeButton({ postId }: { postId: string }) {
  const fetcher = useFetcher();
  
  const handleLike = () => {
    fetcher.submit(
      { postId, action: "like" },
      { method: "post", action: "/api/posts/like" }
    );
  };
  
  return (
    <button 
      onClick={handleLike}
      disabled={fetcher.state === "submitting"}
    >
      {fetcher.state === "submitting" ? "Liking..." : "Like"}
    </button>
  );
}

useMatches

Access information about all matched routes in the current route hierarchy.

/**
 * Returns information about all matched routes in the current hierarchy
 * Includes route IDs, pathnames, data, and route-specific metadata
 * @returns Array of matched route information
 */
function useMatches(): UIMatch[];

interface UIMatch {
  /** Unique identifier for the route */
  id: string;
  /** Path pattern that matched */
  pathname: string;
  /** Route parameters */
  params: Params;
  /** Data from the route's loader */
  data: unknown;
  /** Custom route handle data */
  handle: RouteHandle | undefined;
}

Usage Examples:

import { useMatches } from "@remix-run/react";

// Breadcrumb navigation
function Breadcrumbs() {
  const matches = useMatches();
  
  const breadcrumbs = matches.filter(match => 
    match.handle?.breadcrumb
  ).map(match => ({
    label: match.handle.breadcrumb(match),
    pathname: match.pathname,
  }));
  
  return (
    <nav>
      {breadcrumbs.map((crumb, index) => (
        <span key={crumb.pathname}>
          {index > 0 && " > "}
          <Link to={crumb.pathname}>{crumb.label}</Link>
        </span>
      ))}
    </nav>
  );
}

// Page title based on route hierarchy
function PageTitle() {
  const matches = useMatches();
  
  const titles = matches
    .map(match => match.handle?.title?.(match))
    .filter(Boolean);
    
  const pageTitle = titles.join(" | ");
  
  useEffect(() => {
    document.title = pageTitle;
  }, [pageTitle]);
  
  return null;
}

Type Definitions

Serialization Types

/**
 * Represents the serialized form of data returned from loaders/actions
 * Handles Date objects, nested objects, and other non-JSON types
 */
type SerializeFrom<T> = T extends (...args: any[]) => infer R
  ? SerializeFrom<R>
  : T extends Date
  ? string
  : T extends object
  ? { [K in keyof T]: SerializeFrom<T[K]> }
  : T;

Route Handle

interface RouteHandle {
  /** Function to generate breadcrumb label */
  breadcrumb?: (match: UIMatch) => React.ReactNode;
  /** Function to generate page title */
  title?: (match: UIMatch) => string;
  /** Custom metadata for the route */
  [key: string]: any;
}

Form Props for Fetcher

interface FormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
  method?: "get" | "post" | "put" | "patch" | "delete";
  action?: string;
  encType?: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain";
  replace?: boolean;
  preventScrollReset?: boolean;
  onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;
}

Implementation Notes

  • Type Safety: All hooks provide full TypeScript inference when used with typed loader/action functions
  • Automatic Revalidation: Data is automatically revalidated when dependencies change
  • Error Handling: Hooks integrate with Remix's error boundary system for proper error handling
  • Concurrent Safety: Multiple fetchers can operate simultaneously without conflicts
  • Memory Management: Fetcher instances are automatically cleaned up when components unmount
  • SSR Compatibility: All hooks work correctly during server-side rendering and client hydration

Install with Tessl CLI

npx tessl i tessl/npm-remix-run--react

docs

application-entry-points.md

data-loading-hooks.md

data-response-utilities.md

document-components.md

forms-and-navigation.md

index.md

react-router-integration.md

type-definitions.md

tile.json