Skip to content

Commit

Permalink
docs: small updates
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelDeBoey committed Aug 23, 2023
1 parent 8cb33ce commit 70e30ee
Show file tree
Hide file tree
Showing 31 changed files with 284 additions and 240 deletions.
16 changes: 8 additions & 8 deletions docs/discussion/00-introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ order: 0

These discussion topics are intended to be read in order. It gives you a linear path to Remix mastery instead of bouncing around from one doc to another. While useful independently, topics may refer to code and concepts from previous chapters.

Built on top of [React Router][reactrouter], Remix is four things:
Built on top of [React Router][react-router], Remix is four things:

1. A compiler
2. A server-side HTTP handler
Expand Down Expand Up @@ -44,7 +44,7 @@ app.all(
);
```

Express (or Node.js) is the actual server, Remix is just a handler on that server. The `"@remix-run/express"` package is called an adapter. Remix handlers are server agnostic. Adapters make them work for a specific server by converting the server's request/response API into the Fetch API on the way in, and then adapting the Fetch Response coming from Remix into the server's response API. Here's some pseudo code of what an adapter does:
Express (or Node.js) is the actual server, Remix is just a handler on that server. The `"@remix-run/express"` package is called an adapter. Remix handlers are server agnostic. Adapters make them work for a specific server by converting the server's request/response API into the Fetch API on the way in, and then adapting the Fetch Response coming from Remix into the server's response API. Here's some pseudocode of what an adapter does:

```ts
export function createRequestHandler({ build }) {
Expand Down Expand Up @@ -138,15 +138,15 @@ export async function action({ request }: ActionArgs) {

You can actually use Remix as just a server-side framework without using any browser JavaScript at all. The route conventions for data loading with `loader`, mutations with `action` and HTML forms, and components that render at URLs, can provide the core feature set of a lot of web projects.

In this way, **Remix scales down**. Not every page in your application needs a bunch of JavaScript in the browser and not every user interaction requires any extra flair than the browser's default behaviors. In Remix you can build it the simple way first, and then scale up without changing the fundamental model. Additionally, the majority of the app works before JavaScript loads in the browser, which makes Remix apps resilient to choppy network conditions by design.
In this way, **Remix scales down**. Not every page in your application needs a bunch of JavaScript in the browser and not every user interaction requires any extra flair than the browser's default behaviors. In Remix, you can build it the simple way first, and then scale up without changing the fundamental model. Additionally, the majority of the app works before JavaScript loads in the browser, which makes Remix apps resilient to choppy network conditions by design.

If you're not familiar with traditional back-end web frameworks, you can think of Remix routes as React components that are already their own API route and already know how to load and submit data to themselves on the server.

## Browser Framework

Once Remix has served the document to the browser, it "hydrates" the page with the browser build's JavaScript modules. This is where we talk a lot about Remix "emulating the browser".

When the user clicks a link, instead of making a round trip to the server for the entire document and all of the assets, Remix simply fetches the data for the next page and updates the UI. This has many performance benefits over making a full-document request:
When the user clicks a link, instead of making a round trip to the server for the entire document and all the assets, Remix simply fetches the data for the next page and updates the UI. This has many performance benefits over making a full-document request:

1. Assets don't need to be re-downloaded (or pulled from cache)
2. Assets don't need to be parsed by the browser again
Expand All @@ -158,15 +158,15 @@ This approach also has UX benefits like not resetting the scroll position of a s

Remix can also prefetch all resources for a page when the user is about to click a link. The browser framework knows about the compiler's asset manifest. It can match the URL of the link, read the manifest, and then prefetch all data, JavaScript modules, and even CSS resources for the next page. This is how Remix apps feel fast even when networks are slow.

Remix then provides client side APIs so you can create rich user experiences without changing the fundamental model of HTML and browsers.
Remix then provides client side APIs, so you can create rich user experiences without changing the fundamental model of HTML and browsers.

Taking our route module from before, here are a few small, but useful UX improvements to the form that you can only do with JavaScript in the browser:

1. Disable the button when the form is being submitted
2. Focus the input when server-side form validation fails
3. Animate in the error messages

```tsx nocopy lines=[4-6,8-12,23-26,30-32]
```tsx lines=[4-6,8-12,23-26,30-32] nocopy
export default function Projects() {
const projects = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
Expand Down Expand Up @@ -213,7 +213,7 @@ Because Remix reaches into the controller level of the backend, it can do this s

And while it doesn't reach as far back into the stack as server-side frameworks like Rails and Laravel, it does reach way farther up the stack into the browser to make the transition from the back end to the front end seamless.

For example. Building a plain HTML form and server-side handler in a back-end heavy web framework is just as easy to do as it is in Remix. But as soon as you want to cross over into an experience with animated validation messages, focus management, and pending UI, it requires a fundamental change in the code. Typically people build an API route and then bring in a splash of client-side JavaScript to connect the two. With Remix you simply add some code around the existing "server side view" without changing how it works fundamentally.
For example. Building a plain HTML form and server-side handler in a back-end heavy web framework is just as easy to do as it is in Remix. But as soon as you want to cross over into an experience with animated validation messages, focus management, and pending UI, it requires a fundamental change in the code. Typically, people build an API route and then bring in a splash of client-side JavaScript to connect the two. With Remix, you simply add some code around the existing "server side view" without changing how it works fundamentally.

We borrowed an old term and called this Progressive Enhancement in Remix. Start small with a plain HTML form (Remix scales down) and then scale the UI up when you have the time and ambition.

Expand All @@ -224,4 +224,4 @@ We borrowed an old term and called this Progressive Enhancement in Remix. Start
[vercel]: https://vercel.com
[netlify]: https://netlify.com
[arc]: https://arc.codes
[reactrouter]: https://reactrouter.com
[react-router]: https://reactrouter.com
8 changes: 4 additions & 4 deletions docs/discussion/01-runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Deploying a Remix application has four layers:
3. A server adapter like `@remix-run/express`
4. A web host or platform

Depending on your web host, you may have fewer layers. For example, deploying to Cloudflare Pages takes care of 2, 3, and 4 all at once. Deploying Remix inside of an express app will have all four, and using the "Remix App Server" combines 2 and 3!
Depending on your web host, you may have fewer layers. For example, deploying to Cloudflare Pages takes care of 2, 3, and 4 all at once. Deploying Remix inside an Express app will have all four, and using the "Remix App Server" combines 2 and 3!

You can wire all of these up yourself, or start with a Remix Template.

Expand Down Expand Up @@ -49,11 +49,11 @@ import { createCookieSessionStorage } from "remix";

## Adapters

Remix is not an HTTP server, but rather a handler inside of an existing HTTP server. Adapters allow the Remix handler to run inside the HTTP server. Some JavaScript runtimes, especially Node.js, have multiple ways to create an HTTP server. For example, in Node.js you can use Express.js, fastify, or raw `http.createServer`.
Remix is not an HTTP server, but rather a handler inside an existing HTTP server. Adapters allow the Remix handler to run inside the HTTP server. Some JavaScript runtimes, especially Node.js, have multiple ways to create an HTTP server. For example, in Node.js you can use Express.js, fastify, or raw `http.createServer`.

Each of these servers has its own Request/Response API. The adapter's job is to convert the incoming request to a Web Fetch Request, run the Remix handler, and then adapt the Web Fetch Response back to the host server's response API.

Here's some pseudo code that illustrates the flow.
Here's some pseudocode that illustrates the flow.

```tsx
// import the app build created by `remix build`
Expand Down Expand Up @@ -92,7 +92,7 @@ See [`@remix-run/serve`][serve]

## Templates

Remix is designed to be incredibly flexible with just enough opinions to connect the UI to the back end but it doesn't bring opinions on the database you use, how you cache data, or where and how your app is deployed.
Remix is designed to be incredibly flexible with just enough opinions to connect the UI to the back end, but it doesn't bring opinions on the database you use, how you cache data, or where and how your app is deployed.

Remix templates are starting points for app development with all of these extra opinions baked in, created by the community.

Expand Down
20 changes: 10 additions & 10 deletions docs/discussion/02-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,18 @@ app
└── sales.tsx
```

- `root.tsx` is the "root route" that serves as the layout for the entire application. Every route will render inside its `<Outlet/>`.
- Note that there are files with `.` delimiters. The `.` creates a `/` in the URL for that route, as well as layout nesting with another route that matches the segments before the `.`. For example, `sales.tsx` is the **parent route** for all the **child routes** that look like `sales.[the nested path].tsx`. The `<Outlet />` in `sales.tsx` will render the matching child route.
- The `_index.tsx` routes will render inside the parent `<Outlet>` when the url is only as deep as the parent's path (like `example.com/sales` instead of `example.com/sales/customers`)
- `app/root.tsx` is the "root route" that serves as the layout for the entire application. Every route will render inside its `<Outlet/>`.
- Note that there are files with `.` delimiters. The `.` creates a `/` in the URL for that route, as well as layout nesting with another route that matches the segments before the `.`. For example, `app/routes/sales.tsx` is the **parent route** for all the **child routes** that look like `app/routes/sales.[the nested path].tsx`. The `<Outlet />` in `app/routes/sales.tsx` will render the matching child route.
- The `app/routes/_index.tsx` routes will render inside the parent `<Outlet>` when the url is only as deep as the parent's path (like `example.com/sales` instead of `example.com/sales/customers`)

## Rendering Route Layout Hierarchies

Let's consider the URL is `/sales/invoices/102000`. The following routes all match that URL:

- `root.tsx`
- `routes/sales.tsx`
- `routes/sales.invoices.tsx`
- `routes/sales.invoices.$invoiceId.tsx`
- `app/root.tsx`
- `app/routes/sales.tsx`
- `app/routes/sales.invoices.tsx`
- `app/routes/sales.invoices.$invoiceId.tsx`

When the user visits this page, Remix will render the components in this hierarchy:

Expand All @@ -68,7 +68,7 @@ When the user visits this page, Remix will render the components in this hierarc
</Root>
```

You'll note that the component hierarchy is perfectly mapped to the file system hierarchy in `routes`. By looking at the files, you can anticipate how they will render.
You'll note that the component hierarchy is perfectly mapped to the file system hierarchy in `app/routes`. By looking at the files, you can anticipate how they will render.

```
app
Expand All @@ -87,7 +87,7 @@ If the URL is `/accounts`, the UI hierarchy changes to this:
</Root>
```

It's partly your job to make this work. You need to render an `<Outlet/>` to continue the rendering of the route hierarchy from the parent routes. `root.tsx` renders the main layout, sidebar, and then an outlet for the child routes to continue rendering through:
It's partly your job to make this work. You need to render an `<Outlet/>` to continue the rendering of the route hierarchy from the parent routes. `app/root.tsx` renders the main layout, sidebar, and then an outlet for the child routes to continue rendering through:

```tsx filename=app/root.tsx lines=[1,7]
import { Outlet } from "@remix-run/react";
Expand Down Expand Up @@ -134,7 +134,7 @@ And index is the thing you render to fill in that empty space when none of the c

Index routes are "leaf routes". They're the end of the line. If you think you need to add child routes to an index route, that usually means your layout code (like a shared nav) needs to move out of the index route and into the parent route.

This usually comes up when folks are just getting started with Remix and put their global nav in `app/routes/_index.tsx`. Move that global nav up into `app/root.tsx`. Everything inside of `app/routes/*` is already a child of `root.tsx`.
This usually comes up when folks are just getting started with Remix and put their global nav in `app/routes/_index.tsx`. Move that global nav up into `app/root.tsx`. Everything inside of `app/routes/*` is already a child of `app/root.tsx`.

### What is the `?index` query param?

Expand Down
44 changes: 27 additions & 17 deletions docs/discussion/03-data-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ export async function action() {

Route files can export a `loader` function that provides data to the route component. When the user navigates to a matching route, the data is first loaded and then the page is rendered.

```tsx filename=routes/account.tsx lines=[1-7]
export async function loader({ request }) {
```tsx filename=routes/account.tsx lines=[1-2,4-10]
import type { LoaderArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno

export async function loader({ request }: LoaderArgs) {
const user = await getUser(request);
return {
return json({
displayName: user.displayName,
email: user.email,
};
});
}

export default function Component() {
Expand All @@ -56,19 +59,21 @@ export async function action() {

The default export of the route file is the component that renders. It reads the loader data with `useLoaderData`:

```tsx lines=[1,11-22]
```tsx lines=[3,13-28]
import type { LoaderArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useLoaderData } from "@remix-run/react";

export function loader({ request }) {
export async function loader({ request }: LoaderArgs) {
const user = await getUser(request);
return {
return json({
displayName: user.displayName,
email: user.email,
};
});
}

export default function Component() {
const user = useLoaderData();
const user = useLoaderData<typeof loader>();
return (
<Form action="/account">
<h1>Settings for {user.displayName}</h1>
Expand All @@ -84,7 +89,7 @@ export default function Component() {
);
}

export function action({ request }) {
export async function action() {
// ...
}
```
Expand All @@ -93,19 +98,24 @@ export function action({ request }) {

Finally, the action on the route matching the form's action attribute is called when the form is submitted. In this example it's the same route. The values in the form fields will be available on the standard `request.formData()` API. Note the `name` attribute on the inputs is coupled to the `formData.get(fieldName)` getter.

```tsx lines=[25-34]
```tsx lines=[2,33-42]
import type {
ActionArgs,
LoaderArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useLoaderData } from "@remix-run/react";

export function loader({ request }) {
export async function loader({ request }: LoaderArgs) {
const user = await getUser(request);
return {
return json({
displayName: user.displayName,
email: user.email,
};
});
}

export default function Component() {
const user = useLoaderData();
const user = useLoaderData<typeof loader>();
return (
<Form action="/account">
<h1>Settings for {user.displayName}</h1>
Expand All @@ -121,15 +131,15 @@ export default function Component() {
);
}

export function action({ request }) {
export async function action({ request }: ActionArgs) {
const user = await getUser(request);

await updateUser(user.id, {
email: formData.get("email"),
displayName: formData.get("displayName"),
});

return { ok: true };
return json({ ok: true });
}
```

Expand Down
17 changes: 11 additions & 6 deletions docs/discussion/04-server-vs-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,25 @@ The following route exports and the dependencies used within them are removed fr
Consider this route module from the last section:

```tsx filename=routes/settings.tsx
import type {
ActionArgs,
LoaderArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useLoaderData } from "@remix-run/react";

import { getUser, updateUser } from "../user";

export function loader({ request }) {
export async function loader({ request }: LoaderArgs) {
const user = await getUser(request);
return {
return json({
displayName: user.displayName,
email: user.email,
};
});
}

export default function Component() {
const user = useLoaderData();
const user = useLoaderData<typeof loader>();
return (
<Form action="/account">
<h1>Settings for {user.displayName}</h1>
Expand All @@ -46,15 +51,15 @@ export default function Component() {
);
}

export function action({ request }) {
export async function action({ request }: ActionArgs) {
const user = await getUser(request);

await updateUser(user.id, {
email: formData.get("email"),
displayName: formData.get("displayName"),
});

return { ok: true };
return json({ ok: true });
}
```

Expand Down
10 changes: 5 additions & 5 deletions docs/discussion/05-react-router.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ title: React Router

# React Router

While Remix works as a multi-page app, when JavaScript is loaded, it uses client side routing for a full Single Page App user experience, with all the speed and network efficiency that comes along with it.
While Remix works as a multipage app, when JavaScript is loaded, it uses client side routing for a full Single Page App user experience, with all the speed and network efficiency that comes along with it.

Remix is built on top of [React Router][react-router] and maintained by the same team. This means that you can use all of the features of React Router in your Remix app.
Remix is built on top of [React Router][react-router] and maintained by the same team. This means that you can use all the features of React Router in your Remix app.

This also means that the 90% of Remix is really just React Router: a very old, very stable library that is perhaps the largest dependency in the React ecosystem. Remix simply adds a server behind it.

## Importing Components and Hooks

Remix Re-exports all of the components and hooks from React Router DOM, so you don't need to install React Router yourself.
Remix Re-exports all the components and hooks from React Router DOM, so you don't need to install React Router yourself.

🚫 Don't do this:

Expand All @@ -22,7 +22,7 @@ import { useLocation } from "react-router-dom";

✅ Do this:

```tsx
```tsx good
import { useLocation } from "@remix-run/react";
```

Expand All @@ -41,7 +41,7 @@ import { Link } from "react-router-dom";

✅ Do this:

```tsx
```tsx good
import { Link } from "@remix-run/react";

// this will prefetch data and assets
Expand Down
Loading

0 comments on commit 70e30ee

Please sign in to comment.