| 1 | ---
|
| 2 | title: Data Loading
|
| 3 | order: 5
|
| 4 | ---
|
| 5 |
|
| 6 | # Data Loading
|
| 7 |
|
| 8 | [MODES: framework]
|
| 9 |
|
| 10 | ## Introduction
|
| 11 |
|
| 12 | Data is provided to the route component from `loader` and `clientLoader`.
|
| 13 |
|
| 14 | 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.
|
| 15 |
|
| 16 | The type for the `loaderData` prop is [automatically generated][type-safety].
|
| 17 |
|
| 18 | <docs-info>We try to support the same set of [serializable types][serializable-types] that React permits server components to pass as props to client components. This future proofs your application for any eventual migration to [RSC][rsc].</docs-info>
|
| 19 |
|
| 20 | ## Client Data Loading
|
| 21 |
|
| 22 | `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.
|
| 23 |
|
| 24 | ```tsx filename=app/product.tsx
|
| 25 | // route("products/:pid", "./product.tsx");
|
| 26 | import type { Route } from "./+types/product";
|
| 27 |
|
| 28 | export async function clientLoader({
|
| 29 | params,
|
| 30 | }: Route.ClientLoaderArgs) {
|
| 31 | const res = await fetch(`/api/products/${params.pid}`);
|
| 32 | const product = await res.json();
|
| 33 | return product;
|
| 34 | }
|
| 35 |
|
| 36 | // HydrateFallback is rendered while the client loader is running
|
| 37 | export function HydrateFallback() {
|
| 38 | return <div>Loading...</div>;
|
| 39 | }
|
| 40 |
|
| 41 | export default function Product({
|
| 42 | loaderData,
|
| 43 | }: Route.ComponentProps) {
|
| 44 | const { name, description } = loaderData;
|
| 45 | return (
|
| 46 | <div>
|
| 47 | <h1>{name}</h1>
|
| 48 | <p>{description}</p>
|
| 49 | </div>
|
| 50 | );
|
| 51 | }
|
| 52 | ```
|
| 53 |
|
| 54 | ## Server Data Loading
|
| 55 |
|
| 56 | 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.
|
| 57 |
|
| 58 | ```tsx filename=app/product.tsx
|
| 59 | // route("products/:pid", "./product.tsx");
|
| 60 | import type { Route } from "./+types/product";
|
| 61 | import { fakeDb } from "../db";
|
| 62 |
|
| 63 | export async function loader({ params }: Route.LoaderArgs) {
|
| 64 | const product = await fakeDb.getProduct(params.pid);
|
| 65 | return product;
|
| 66 | }
|
| 67 |
|
| 68 | export default function Product({
|
| 69 | loaderData,
|
| 70 | }: Route.ComponentProps) {
|
| 71 | const { name, description } = loaderData;
|
| 72 | return (
|
| 73 | <div>
|
| 74 | <h1>{name}</h1>
|
| 75 | <p>{description}</p>
|
| 76 | </div>
|
| 77 | );
|
| 78 | }
|
| 79 | ```
|
| 80 |
|
| 81 | 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.
|
| 82 |
|
| 83 | ## Static Data Loading
|
| 84 |
|
| 85 | When pre-rendering, loaders are used to fetch data during the production build.
|
| 86 |
|
| 87 | ```tsx filename=app/product.tsx
|
| 88 | // route("products/:pid", "./product.tsx");
|
| 89 | import type { Route } from "./+types/product";
|
| 90 |
|
| 91 | export async function loader({ params }: Route.LoaderArgs) {
|
| 92 | let product = await getProductFromCSVFile(params.pid);
|
| 93 | return product;
|
| 94 | }
|
| 95 |
|
| 96 | export default function Product({
|
| 97 | loaderData,
|
| 98 | }: Route.ComponentProps) {
|
| 99 | const { name, description } = loaderData;
|
| 100 | return (
|
| 101 | <div>
|
| 102 | <h1>{name}</h1>
|
| 103 | <p>{description}</p>
|
| 104 | </div>
|
| 105 | );
|
| 106 | }
|
| 107 | ```
|
| 108 |
|
| 109 | The URLs to pre-render are specified in `react-router.config.ts`:
|
| 110 |
|
| 111 | ```ts filename=react-router.config.ts
|
| 112 | import type { Config } from "@react-router/dev/config";
|
| 113 |
|
| 114 | export default {
|
| 115 | async prerender() {
|
| 116 | let products = await readProductsFromCSVFile();
|
| 117 | return products.map(
|
| 118 | (product) => `/products/${product.id}`,
|
| 119 | );
|
| 120 | },
|
| 121 | } satisfies Config;
|
| 122 | ```
|
| 123 |
|
| 124 | Note that when server rendering, any URLs that aren't pre-rendered will be server rendered as usual, allowing you to pre-render some data at a single route while still server rendering the rest.
|
| 125 |
|
| 126 | ## Using Both Loaders
|
| 127 |
|
| 128 | `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.
|
| 129 |
|
| 130 | ```tsx filename=app/product.tsx
|
| 131 | // route("products/:pid", "./product.tsx");
|
| 132 | import type { Route } from "./+types/product";
|
| 133 | import { fakeDb } from "../db";
|
| 134 |
|
| 135 | export async function loader({ params }: Route.LoaderArgs) {
|
| 136 | return fakeDb.getProduct(params.pid);
|
| 137 | }
|
| 138 |
|
| 139 | export async function clientLoader({
|
| 140 | serverLoader,
|
| 141 | params,
|
| 142 | }: Route.ClientLoaderArgs) {
|
| 143 | const res = await fetch(`/api/products/${params.pid}`);
|
| 144 | const serverData = await serverLoader();
|
| 145 | return { ...serverData, ...(await res.json()) };
|
| 146 | }
|
| 147 |
|
| 148 | export default function Product({
|
| 149 | loaderData,
|
| 150 | }: Route.ComponentProps) {
|
| 151 | const { name, description } = loaderData;
|
| 152 |
|
| 153 | return (
|
| 154 | <div>
|
| 155 | <h1>{name}</h1>
|
| 156 | <p>{description}</p>
|
| 157 | </div>
|
| 158 | );
|
| 159 | }
|
| 160 | ```
|
| 161 |
|
| 162 | 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.
|
| 163 |
|
| 164 | ```tsx filename=app/product.tsx
|
| 165 | export async function loader() {
|
| 166 | /* ... */
|
| 167 | }
|
| 168 |
|
| 169 | export async function clientLoader() {
|
| 170 | /* ... */
|
| 171 | }
|
| 172 |
|
| 173 | // force the client loader to run during hydration
|
| 174 | clientLoader.hydrate = true as const; // `as const` for type inference
|
| 175 |
|
| 176 | export function HydrateFallback() {
|
| 177 | return <div>Loading...</div>;
|
| 178 | }
|
| 179 |
|
| 180 | export default function Product() {
|
| 181 | /* ... */
|
| 182 | }
|
| 183 | ```
|
| 184 |
|
| 185 | ---
|
| 186 |
|
| 187 | Next: [Actions][actions]
|
| 188 |
|
| 189 | See also:
|
| 190 |
|
| 191 | - [Streaming with Suspense][streaming]
|
| 192 | - [Client Data][client-data]
|
| 193 | - [Using Fetchers][fetchers]
|
| 194 |
|
| 195 | [type-safety]: ../../explanation/type-safety
|
| 196 | [serializable-types]: https://react.dev/reference/rsc/use-client#serializable-types
|
| 197 | [rsc]: ../../how-to/react-server-components
|
| 198 | [actions]: ./actions
|
| 199 | [streaming]: ../../how-to/suspense
|
| 200 | [client-data]: ../../how-to/client-data
|
| 201 | [fetchers]: ../../how-to/fetchers#loading-data
|