Skip to content

Commit

Permalink
Add draft release notes and update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed May 10, 2024
1 parent 9df0663 commit 4cdf884
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 29 deletions.
70 changes: 70 additions & 0 deletions CHANGELOG.md
Expand Up @@ -167,6 +167,76 @@ Date: YYYY-MM-DD
-->

## v2.9.2

Date: 2024-05-10

### What's Changed

#### Updated Type-Safety for Single Fetch

In 2.9.2 we've enhanced the type-safety when opting into the `future.unstable_singleFetch` feature. Previously, we added the `response` stub to `LoaderFunctionArgs` and used type overrides for inference on `useLoaderData`, etc., but we found that it wasn't quite enough.

With this release we're introducing new functions to assist the type-inference when using single fetch - `defineLoader`/`defineAction` and their client-side counterparts `defineClientLoader` and nd `defineClientAction`. These are identity functions; they don't modify your loader or action at runtime. Rather, they exist solely for type-safety by providing types for args and by ensuring valid return types.

```ts
export let loader = defineLoader(({ request }) => {
// ^? Request
return { a: 1, b: () => 2 };
// ^ type error: `b` is not serializable
});
```

Note that `defineLoader` and `defineAction` are not technically necessary for defining loaders and actions if you aren't concerned with type-safety:

```ts
// this totally works! and typechecking is happy too!
export let loader = () => {
return { a: 1 };
};
```

This means that you can opt-in to `defineLoader` incrementally, one loader at a time.

Please see the [Single Fetch docs](https://remix.run/docs/en/main/guides/single-fetch) for more information.

### Patch Changes

- `@remix-run/dev` - Vite: Fix `dest already exists` error when running `remix vite:build` ([#9305](https://github.com/remix-run/remix/pull/9305))
- `@remix-run/dev` - Vite: Fix issue resolving critical CSS during development when route files are located outside of the app directory ([#9194](https://github.com/remix-run/remix/pull/9194))
- `@remix-run/dev` - Vite: Remove `@remix-run/node` from Vite plugin's `optimizeDeps.include` list since it was unnecessary and resulted in Vite warnings when not depending on this package ([#9287](https://github.com/remix-run/remix/pull/9287))
- `@remix-run/dev` - Vite: Clean up redundant `?client-route=1` imports in development ([#9395](https://github.com/remix-run/remix/pull/9395))
- `@remix-run/dev` - Vite: Ensure Babel config files are not referenced when applying the `react-refresh` Babel transform within the Remix Vite plugin ([#9241](https://github.com/remix-run/remix/pull/9241))
- `@remix-run/react` - Type-safety for single-fetch: `defineLoader`, `defineClientLoader`, `defineAction`, `defineClientAction` ([#9372](https://github.com/remix-run/remix/pull/9372))
- `@remix-run/react` - Single Fetch: Add `undefined` to `useActionData` type override ([#9322](https://github.com/remix-run/remix/pull/9322))
- `@remix-run/react` - Single Fetch: Allow a `nonce` to be set on single fetch stream transfer inline scripts via `<RemixServer>` ([#9364](https://github.com/remix-run/remix/pull/9364))
- `@remix-run/server-runtime` - Single Fetch: Don't log thrown response stubs via `handleError` ([#9369](https://github.com/remix-run/remix/pull/9369))
- `@remix-run/server-runtime` - Single Fetch: Automatically wrap resource route naked object returns in `json()` for back-compat in v2 (and log deprecation warning) ([#9349](https://github.com/remix-run/remix/pull/9349))
- `@remix-run/server-runtime` - Single Fetch: Pass `response` stub to resource route handlers ([#9349](https://github.com/remix-run/remix/pull/9349))

### Updated Dependencies

- [`react-router-dom@6.23.1`](https://github.com/remix-run/react-router/releases/tag/react-router%406.23.1)
- [`@remix-run/router@1.16.1`](https://github.com/remix-run/react-router/blob/main/packages/router/CHANGELOG.md#1161)

### Changes by Package

- [`@remix-run/cloudflare`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-cloudflare/CHANGELOG.md#292)
- [`@remix-run/cloudflare-pages`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-cloudflare-pages/CHANGELOG.md#292)
- [`@remix-run/cloudflare-workers`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-cloudflare-workers/CHANGELOG.md#292)
- [`@remix-run/css-bundle`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-css-bundle/CHANGELOG.md#292)
- [`@remix-run/deno`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-deno/CHANGELOG.md#292)
- [`@remix-run/dev`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-dev/CHANGELOG.md#292)
- [`@remix-run/eslint-config`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-eslint-config/CHANGELOG.md#292)
- [`@remix-run/express`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-express/CHANGELOG.md#292)
- [`@remix-run/node`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-node/CHANGELOG.md#292)
- [`@remix-run/react`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-react/CHANGELOG.md#292)
- [`@remix-run/serve`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-serve/CHANGELOG.md#292)
- [`@remix-run/server-runtime`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-server-runtime/CHANGELOG.md#292)
- [`@remix-run/testing`](https://github.com/remix-run/remix/blob/remix%402.9.2/packages/remix-testing/CHANGELOG.md#292)

**Full Changelog**: [`v2.9.1...v2.9.2`](https://github.com/remix-run/remix/compare/remix@2.9.1...remix@2.9.2)

## v2.9.1

Date: 2024-04-24
Expand Down
80 changes: 51 additions & 29 deletions docs/guides/single-fetch.md
Expand Up @@ -29,14 +29,16 @@ Previously, Remix used `JSON.stringify` to serialize your loader/action data ove

With Single Fetch, Remix now uses [`turbo-stream`][turbo-stream] under the hood which provides first class support for streaming and allows you to automatically serialize/deserialize more complex data than JSON. The following data types can be streamed down directly via `turbo-stream`: `BigInt`, `Date`, `Error`, `Map`, `Promise`, `RegExp`, `Set`, `Symbol`, and `URL`. Subtypes of `Error` are also supported as long as they have a globally available constructor on the client (`SyntaxError`, `TypeError`, etc.).

This may or may not require any changes to your code once enabling Single Fetch:
This may or may not require any immediate changes to your code once enabling Single Fetch:

-`json` responses returned from `loader`/`action` functions will still be serialized via `JSON.stringify` so if you return a `Date`, you'll receive a `string` from `useLoaderData`/`useActionData`
- ⚠️ If you're returning a `defer` instance or a naked object, it will now be serialized via `turbo-stream`, so if you return a `Date`, you'll receive a `Date` from `useLoaderData`/`useActionData`
- If you wish to maintain current behavior (excluding streaming `defer` responses), you may just wrap any existing naked object returns in `json`

This also means that you no longer need to use the `defer` utility to send `Promise` instances over the wire! You can include a `Promise` anywhere in a naked object and pick it up on `useLoaderData().whatever`. You can also nest `Promise`'s if needed - but beware of potential UX implications.

Once adopting Single Fetch, it is recommended that you incrementally remove the usage of `json`/`defer` throughout your application in favor of returning raw objects.

### React Rendering APIs

In order to maintain consistency between document and data requests, `turbo-stream` is also used as the format for sending down data in initial document requests. This means that once opted-into Single Fetch, your application can no longer use [`renderToString`][rendertostring] and must use a React streaming renderer API such as [`renderToPipeableStream`][rendertopipeablestream] or [`renderToReadableStream`][rendertoreadablestream]) in [`entry.server.tsx`][entry-server].
Expand Down Expand Up @@ -113,7 +115,7 @@ In order to ensure you get the proper types when using Single Fetch, we've inclu

🚨 Make sure the single-fetch types come after any other Remix packages in `types` so that they override those existing types.

#### loader/action definition utilities
#### Loader/Action Definition Utilities

To enhance type-safety when defining loaders and actions with single fetch, you can use the new `unstable_defineLoader` and `unstable_defineAction` utilities:

Expand Down Expand Up @@ -161,6 +163,33 @@ type Serializable =
| Promise<Serializable>;
```

There are also client-side equivalents un `defineClientLoader`/`defineClientAction` that don't have the same return value restrictions because data returned from `clientLoader`/`clientAction` does not need to be serialized over the wire:

```ts
import { unstable_defineLoader as defineLoader } from "@remix-run/node";
import { unstable_defineClientLoader as defineClientLoader } from "@remix-run/react";

export const loader = defineLoader(() => {
return { msg: "Hello!", date: new Date() };
});

export const clientLoader = defineClientLoader(
async ({ serverLoader }) => {
const data = await serverLoader<typeof loader>();
// ^? { msg: string, date: Date }
return {
...data,
client: "World!",
};
}
);

export default function Component() {
const data = useLoaderData<typeof clientLoader>();
// ^? { msg: string, date: Date, client: string }
}
```

<docs-info>These utilities are primarily for type inference on `useLoaderData` and it's equivalents. If you have a resource route that returns a `Response` and is not consumed by Remix APIs (such as `useFetcher`) than you can just stick with your normal `loader`/`action` definitions. Converting those routes to use `defineLoader`/`defineAction` would cause type errors because `turbo-stream` cannot serialize a `Response` instance.</docs-info>

#### `useLoaderData`, `useActionData`, `useRouteLoaderData`, `useFetcher`
Expand Down Expand Up @@ -234,39 +263,32 @@ Instead, your `loader`/`action` functions now receive a mutable `ResponseStub` u
- `response.headers.delete(name)`

```ts
type ResponseStub = {
status: number | undefined;
headers: Headers;
};

export async function action({
request,
response,
}: {
request: Request;
response?: ResponseStub;
}) {
if (!loggedIn(request)) {
response.status = 401;
response.headers.append("Set-Cookie", "foo=bar");
return { message: "Invalid Submission! " };
export const action = defineAction(
async ({ request, response }) => {
if (!loggedIn(request)) {
response.status = 401;
response.headers.append("Set-Cookie", "foo=bar");
return { message: "Invalid Submission! " };
}
await addItemToDb(request);
return null;
}
await addItemToDb(request);
return null;
}
);
```

You can also throw these response stubs to short circuit the flow of your loaders and actions:

```tsx
export async function loader({ request, response }) {
if (shouldRedirectToHome(request)) {
response.status = 302;
response.headers.set("Location", "/");
throw response;
export const loader = defineLoader(
({ request, response }) => {
if (shouldRedirectToHome(request)) {
response.status = 302;
response.headers.set("Location", "/");
throw response;
}
// ...
}
// ...
}
);
```

Each `loader`/`action` receives it's own unique `response` instance so you cannot see what other `loader`/`action` functions have set (which would be subject to race conditions). The resulting HTTP Response status and headers are determined as follows:
Expand All @@ -285,7 +307,7 @@ Because single fetch supports naked object returns, and you no longer need to re

### Client Loaders

If your app has route using [`clientLoader`][client-loader] functions, it's important to note that the behavior of Single Fetch will change slightly. Because `clientLoader` is intended to give you a way to opt-out of calling the server `loader` function - it would be incorrect for the Single Fetch call to execute that server loader. But we run all loaders in parallel and we don't want to _wait_ to make the call until we know which `clientLoader`'s are actually asking for server data.
If your app has routes using [`clientLoader`][client-loader] functions, it's important to note that the behavior of Single Fetch will change slightly. Because `clientLoader` is intended to give you a way to opt-out of calling the server `loader` function - it would be incorrect for the Single Fetch call to execute that server loader. But we run all loaders in parallel and we don't want to _wait_ to make the call until we know which `clientLoader`'s are actually asking for server data.

For example, consider the following `/a/b/c` routes:

Expand Down

0 comments on commit 4cdf884

Please sign in to comment.