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