Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@
- ryanflorence
- stephanerangaya
- zachdtaylor
- tylerbrostrom
- ascorbic
- IAmLuisJ
- matmilbury
128 changes: 66 additions & 62 deletions docs/api/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export default function handleRequest(
responseHeaders: Headers,
remixContext: EntryContext
) {
let markup = ReactDOMServer.renderToString(
const markup = ReactDOMServer.renderToString(
<RemixServer context={remixContext} url={request.url} />
);

Expand All @@ -179,14 +179,15 @@ export default function handleRequest(
}

// this is an optional export
export let handleDataRequest: HandleDataRequestFunction = (
response: Response,
// same args that get passed to the action or loader that was called
{ request, params, context }
) => {
response.headers.set("x-custom", "yay!");
return response;
};
export const handleDataRequest: HandleDataRequestFunction =
(
response: Response,
// same args that get passed to the action or loader that was called
{ request, params, context }
) => {
response.headers.set("x-custom", "yay!");
return response;
};
```

## Route Module API
Expand Down Expand Up @@ -215,13 +216,13 @@ export default function SomeRouteComponent() {
Each route can define a "loader" function that will be called on the server before rendering to provide data to the route.

```tsx
export let loader = async () => {
export const loader = async () => {
return { ok: true };
};

// Typescript
import type { LoaderFunction } from "remix";
export let loader: LoaderFunction = async () => {
export const loader: LoaderFunction = async () => {
return { ok: true };
};
```
Expand All @@ -234,12 +235,12 @@ Using the database ORM Prisma as an example:
import { useLoaderData } from "remix";
import { prisma } from "../db";

export let loader = async () => {
export const loader = async () => {
return prisma.user.findMany();
};

export default function Users() {
let data = useLoaderData();
const data = useLoaderData();
return (
<ul>
{data.map(user => (
Expand All @@ -260,7 +261,7 @@ Route params are passed to your loader. If you have a loader at `data/invoices/$

```js
// if the user visits /invoices/123
export let loader: LoaderFunction = ({ params }) => {
export const loader: LoaderFunction = ({ params }) => {
params.invoiceId; // "123"
};
```
Expand All @@ -272,13 +273,13 @@ This is a [Fetch Request][request] instance with information about the request.
Most common cases are reading headers or the URL. You can also use this to read URL [URLSearchParams][urlsearchparams] from the request like so:

```tsx
export let loader: LoaderFunction = ({ request }) => {
export const loader: LoaderFunction = ({ request }) => {
// read a cookie
let cookie = request.headers.get("Cookie");
const cookie = request.headers.get("Cookie");

// parse the search params
let url = new URL(request.url);
let search = url.searchParams.get("search");
const url = new URL(request.url);
const search = url.searchParams.get("search");
};
```

Expand Down Expand Up @@ -309,8 +310,8 @@ app.all(
And then your loader can access it.

```ts filename=routes/some-route.tsx
export let loader: LoaderFunction = ({ context }) => {
let { expressUser } = context;
export const loader: LoaderFunction = ({ context }) => {
const { expressUser } = context;
// ...
};
```
Expand All @@ -320,7 +321,7 @@ export let loader: LoaderFunction = ({ context }) => {
You can return plain JavaScript objects from your loaders that will be made available to your [route modules]("../route-module").

```ts
export let loader = async () => {
export const loader = async () => {
return { whatever: "you want" };
};
```
Expand All @@ -330,9 +331,9 @@ export let loader = async () => {
When you return a plain object, Remix turns it into a [Fetch Response][response]. This means you can return them yourself, too.

```js
export let loader: LoaderFunction = async () => {
let users = await db.users.findMany();
let body = JSON.stringify(users);
export const loader: LoaderFunction = async () => {
const users = await db.users.findMany();
const body = JSON.stringify(users);
return new Response(body, {
headers: {
"Content-Type": "application/json"
Expand All @@ -346,8 +347,8 @@ Remix provides helpers, like `json`, so you don't have to construct them yoursel
```tsx
import { json } from "remix";

export let loader: LoaderFunction = async () => {
let users = await fakeDb.users.findMany();
export const loader: LoaderFunction = async () => {
const users = await fakeDb.users.findMany();
return json(users);
};
```
Expand All @@ -357,8 +358,10 @@ Between these two examples you can see how `json` just does a little of work to
```tsx
import { json } from "remix";

export let loader: LoaderFunction = async ({ params }) => {
let user = await fakeDb.project.findOne({
export const loader: LoaderFunction = async ({
params
}) => {
const user = await fakeDb.project.findOne({
where: { id: params.id }
});

Expand Down Expand Up @@ -391,7 +394,7 @@ export type InvoiceNotFoundResponse = ThrownResponse<
>;

export function getInvoice(id, user) {
let invoice = db.invoice.find({ where: { id } });
const invoice = db.invoice.find({ where: { id } });
if (invoice === null) {
throw json("Not Found", { status: 404 });
}
Expand All @@ -404,7 +407,7 @@ import { redirect } from "remix";
import { getSession } from "./session";

function requireUserSession(request) {
let session = await getSession(
const session = await getSession(
request.headers.get("cookie")
);
if (!session) {
Expand Down Expand Up @@ -435,12 +438,12 @@ type ThrownResponses =
| InvoiceNotFoundResponse
| ThrownResponse<401, InvoiceCatchData>;

export let loader = async ({ request, params }) => {
let user = await requireUserSession(request);
let invoice: Invoice = getInvoice(params.invoiceId);
export const loader = async ({ request, params }) => {
const user = await requireUserSession(request);
const invoice: Invoice = getInvoice(params.invoiceId);

if (!invoice.userIds.includes(user.id)) {
let data: InvoiceCatchData = {
const data: InvoiceCatchData = {
invoiceOwnerEmail: invoice.owner.email
};
throw new json(data, { status: 401 });
Expand All @@ -450,13 +453,13 @@ export let loader = async ({ request, params }) => {
};

export default function InvoiceRoute() {
let invoice = useLoaderData<Invoice>();
const invoice = useLoaderData<Invoice>();
return <InvoiceView invoice={invoice} />;
}

export function CatchBoundary() {
// this returns { status, statusText, data }
let caught = useCatch<ThrownResponses>();
const caught = useCatch<ThrownResponses>();

switch (caught.status) {
case 401:
Expand Down Expand Up @@ -502,15 +505,15 @@ export async function loader() {
}

export async function action({ request }) {
let body = await request.formData();
let todo = await fakeCreateTodo({
const body = await request.formData();
const todo = await fakeCreateTodo({
title: body.get("title")
});
return redirect(`/todos/${todo.id}`);
}

export default function Todos() {
let data = useLoaderData();
const data = useLoaderData();
return (
<div>
<TodoList todos={data} />
Expand Down Expand Up @@ -596,16 +599,16 @@ That is all to say that Remix has given you a very large gun with which to shoot
import parseCacheControl from "parse-cache-control";

export function headers({ loaderHeaders, parentHeaders }) {
let loaderCache = parseCacheControl(
const loaderCache = parseCacheControl(
loaderHeaders.get("Cache-Control")
);
let parentCache = parseCacheControl(
const parentCache = parseCacheControl(
parentHeaders.get("Cache-Control")
);

// take the most conservative between the parent and loader, otherwise
// we'll be too aggressive for one of them.
let maxAge = Math.min(
const maxAge = Math.min(
loaderCache["max-age"],
parentCache["max-age"]
);
Expand All @@ -625,7 +628,7 @@ The meta export will set meta tags for your html document. We highly recommend s
```tsx
import type { MetaFunction } from "remix";

export let meta: MetaFunction = () => {
export const meta: MetaFunction = () => {
return {
title: "Something cool",
description:
Expand All @@ -645,7 +648,7 @@ The links function defines which `<link>` elements to add to the page when the u
```tsx
import type { LinksFunction } from "remix";

export let links: LinksFunction = () => {
export const links: LinksFunction = () => {
return [
{
rel: "icon",
Expand Down Expand Up @@ -680,7 +683,7 @@ Examples:
import type { LinksFunction } from "remix";
import stylesHref from "../styles/something.css";

export let links: LinksFunction = () => {
export const links: LinksFunction = () => {
return [
// add a favicon
{
Expand Down Expand Up @@ -735,7 +738,7 @@ Examples:
```tsx
import type { MetaFunction } from "remix";

export let meta: MetaFunction = () => {
export const meta: MetaFunction = () => {
return {
title: "Josie's Shake Shack", // <title>Josie's Shake Shack</title>
description: "Delicious shakes", // <meta name="description" content="Delicious shakes">
Expand Down Expand Up @@ -772,7 +775,7 @@ A `CatchBoundary` component has access to the status code and thrown response da
import { useCatch } from "remix";

export function CatchBoundary() {
let caught = useCatch();
const caught = useCatch();

return (
<div>
Expand Down Expand Up @@ -814,7 +817,7 @@ export function ErrorBoundary({ error }) {
Exporting a handle allows you to create application conventions with the `useMatches()` hook. You can put whatever values you want on it:

```js
export let handle = {
export const handle = {
its: "all yours"
};
```
Expand All @@ -832,19 +835,20 @@ This function lets apps optimize which routes should be reloaded on some client-
```ts
import type { ShouldReloadFunction } from "remix";

export let unstable_shouldReload: ShouldReloadFunction = ({
// same params that go to `loader` and `action`
params,
export const unstable_shouldReload: ShouldReloadFunction =
({
// same params that go to `loader` and `action`
params,

// a possible form submission that caused this to be reloaded
submission,
// a possible form submission that caused this to be reloaded
submission,

// the next URL being used to render this page
url,
// the next URL being used to render this page
url,

// the previous URL used to render this page
prevUrl
}) => false; // or `true`;
// the previous URL used to render this page
prevUrl
}) => false; // or `true`;
```

During client-side transitions, Remix will optimize reloading of routes that are already rendering, like not reloading layout routes that aren't changing. In other cases, like form submissions or search param changes, Remix doesn't know which routes need to be reloaded so it reloads them all to be safe. This ensures data mutations from the submission or changes in the search params are reflected across the entire page.
Expand All @@ -867,7 +871,7 @@ Here are a couple of common use-cases:
It's common for root loaders to return data that never changes, like environment variables to be sent to the client app. In these cases you never need the root loader to be called again. For this case, you can simply `return false`.

```js [10]
export let loader = () => {
export const loader = () => {
return {
ENV: {
CLOUDINARY_ACCT: process.env.CLOUDINARY_ACCT,
Expand All @@ -876,7 +880,7 @@ export let loader = () => {
};
};

export let unstable_shouldReload = () => false;
export const unstable_shouldReload = () => false;
```

With this in place, Remix will no longer make a request to your root loader for any reason, not after form submissions, not after search param changes, etc.
Expand Down Expand Up @@ -913,7 +917,7 @@ The `$activity.tsx` loader can use the search params to filter the list, so visi

```js [2,7]
export function loader({ request, params }) {
let url = new URLSearchParams(request.url);
const url = new URLSearchParams(request.url);
return exampleDb.activity.findAll({
where: {
projectId: params.projectId,
Expand Down Expand Up @@ -979,7 +983,7 @@ import type { LinksFunction } from "remix";
import styles from "./styles/app.css";
import banner from "./images/banner.jpg";

export let links: LinksFunction = () => {
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};

Expand Down
Loading