🚧 These docs are under construction 🚧
This page was adapted from the React Router docs. See original

Data Loading

Data is provided to the route component from loader and clientLoader.

Loader data is automatically serialized from loaders and deserialized in components. In addition to primitive values like strings and numbers, loaders can return promises, maps, sets, dates and more.

Client Data Loading

clientLoader is used to fetch data on the client. This is useful for pages or full projects that you'd prefer to fetch data from the browser only.

export async function clientLoader({ params }: ClientLoaderFunctionArgs) {
  const res = await fetch(`/api/products/${params.pid}`);
  const product = await res.json();
  return product;
}

// HydrateFallback is rendered while the client loader is running
export function HydrateFallback() {
  return <div>Loading...</div>;
}

export default function Product() {
  const { name, description } = useLoaderData<typeof clientLoader>();
  return (
    <div>
      <h1>{name}</h1>
      <p>{description}</p>
    </div>
  );
}

Server Data Loading

When server rendering, loader is used for both initial page loads and client navigations. Client navigations call the loader through an automatic fetch by React Router from the browser to your server.

import { fakeDb } from "../db";

export async function loader({ params }: LoaderFunctionArgs) {
  const product = await fakeDb.getProduct(params.pid);
  return product;
}

export default function Product() {
  const { name, description } = useLoaderData<typeof loader>();
  return (
    <div>
      <h1>{name}</h1>
      <p>{description}</p>
    </div>
  );
}

Note that the loader function is removed from client bundles so you can use server only APIs without worrying about them being included in the browser.

Durable Objects for Data Loading

Orange allows for Durable Objects to be used for loaders by creating a class that extends RouteDurableObject and has a static id method or a constant id property. RouteDurableObject is a wrapper around the Cloudflare Durable Object with Orange specific features.

import { RouteDurableObject, IdentifierLoaderArgs } from "@orange-js/orange";

export class User extends RouteDurableObject<Env> {
  static id({ params }: IdentifierLoaderArgs) {
    return params.username;
  }

  async loader() {
    const { sql } = this.ctx.storage;
    return sql.exec("SELECT * FROM favorites").toArray();
  }
}

export default function User() {
  const { favorites } = useDurableObject<typeof User>();
  return (
    <div>
      {favorites.map((favorite) => <Favorite name={favorite} />)}
    </div>
  );
}

function Favorite({ name }: { name: string }) { 
  return <div>{name}</div>;
}

Differences with plain DurableObject:

  • Automatically included in the virtual:orange/entrypoints module.
  • webSocketConnect - A handler for incoming WebSocket connections.

Using Both Loaders

loader and clientLoader can be used together. The loader will be used on the server for initial SSR (or pre-rendering) and the clientLoader will be used on subsequent client-side navigations.

import { fakeDb } from "../db";

export async function loader({ params }: LoaderFunctionArgs) {
  return fakeDb.getProduct(params.pid);
}

export async function clientLoader({ params }: ClientLoaderFunctionArgs) {
  const res = await fetch(`/api/products/${params.pid}`);
  const serverData = await serverLoader();
  return { ...serverData, ...res.json() };
}

export default function Product() {
  const { name, description } = useLoaderData<typeof clientLoader>();

  return (
    <div>
      <h1>{name}</h1>
      <p>{description}</p>
    </div>
  );
}

You can also force the client loader to run during hydration and before the page renders by setting the hydrate property on the function. In this situation you will want to render a HydrateFallback component to show a fallback UI while the client loader runs.

export async function loader() {
  /* ... */
}

export async function clientLoader() {
  /* ... */
}

// force the client loader to run during hydration
clientLoader.hydrate = true as const; // `as const` for type inference

export function HydrateFallback() {
  return <div>Loading...</div>;
}

export default function Product() {
  /* ... */
}

Next: Actions