Skip to content

Commit

Permalink
docs: fix useActionData/useLoaderData usage (remix-run#4835)
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelDeBoey authored Jan 18, 2023
1 parent fa76f7c commit e1a4469
Show file tree
Hide file tree
Showing 18 changed files with 158 additions and 156 deletions.
28 changes: 10 additions & 18 deletions docs/file-conventions/routes-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,17 @@ For example: `app/routes/blog/$postId.tsx` will match the following URLs:
On each of these pages, the dynamic segment of the URL path is the value of the parameter. There can be multiple parameters active at any time (as in `/dashboard/:client/invoices/:invoiceId` [view example app][view-example-app]) and all parameters can be accessed within components via [`useParams`][use-params] and within loaders/actions via the argument's [`params`][params] property:

```tsx filename=app/routes/blog/$postId.tsx
import { useParams } from "@remix-run/react";
import type {
LoaderFunction,
ActionFunction,
ActionArgs,
LoaderArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { useParams } from "@remix-run/react";

export const loader: LoaderFunction = async ({
params,
}) => {
export const loader = async ({ params }: LoaderArgs) => {
console.log(params.postId);
};

export const action: ActionFunction = async ({
params,
}) => {
export const action = async ({ params }: ActionArgs) => {
console.log(params.postId);
};

Expand Down Expand Up @@ -244,21 +240,17 @@ Files that are named `$.tsx` are called "splat" (or "catch-all") routes. These r
Similar to dynamic route parameters, you can access the value of the matched path on the splat route's `params` with the `"*"` key.

```tsx filename=app/routes/$.tsx
import { useParams } from "@remix-run/react";
import type {
LoaderFunction,
ActionFunction,
ActionArgs,
LoaderArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { useParams } from "@remix-run/react";

export const loader: LoaderFunction = async ({
params,
}) => {
export const loader = async ({ params }: LoaderArgs) => {
console.log(params["*"]);
};

export const action: ActionFunction = async ({
params,
}) => {
export const action = async ({ params }: ActionArgs) => {
console.log(params["*"]);
};

Expand Down
15 changes: 8 additions & 7 deletions docs/guides/file-uploads.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ Most of the time, you'll probably want to proxy the file to a file host.
**Example:**

```tsx
import type { UploadHandler } from "@remix-run/{runtime}";
import type {
ActionArgs,
UploadHandler,
} from "@remix-run/node"; // or cloudflare/deno
import {
unstable_composeUploadHandlers,
unstable_createMemoryUploadHandler,
} from "@remix-run/{runtime}";
// writeAsyncIterableToWritable is a Node-only utility
import { writeAsyncIterableToWritable } from "@remix-run/node";
unstable_parseMultipartFormData,
} from "@remix-run/node"; // or cloudflare/deno
import { writeAsyncIterableToWritable } from "@remix-run/node"; // `writeAsyncIterableToWritable` is a Node-only utility
import type {
UploadApiOptions,
UploadApiResponse,
Expand Down Expand Up @@ -51,9 +54,7 @@ async function uploadImageToCloudinary(
return uploadPromise;
}

export const action: ActionFunction = async ({
request,
}) => {
export const action = async ({ request }: ActionArgs) => {
const userId = getUserId(request);

const uploadHandler = unstable_composeUploadHandlers(
Expand Down
10 changes: 5 additions & 5 deletions docs/guides/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,14 +305,14 @@ Prefixing a file name with `$` will make that route path a **dynamic segment**.

For example, the `$invoiceId.jsx` route. When the url is `/sales/invoices/102000`, Remix will provide the string value `102000` to your loaders, actions, and components by the same name as the filename segment:

```jsx
```tsx
import { useParams } from "@remix-run/react";

export async function loader({ params }) {
export async function loader({ params }: LoaderArgs) {
const id = params.invoiceId;
}

export async function action({ params }) {
export async function action({ params }: ActionArgs) {
const id = params.invoiceId;
}

Expand Down Expand Up @@ -361,8 +361,8 @@ app

When the URL is `example.com/files/images/work/flyer.jpg`. The splat param will capture the trailing segments of the URL and be available to your app on `params["*"]`

```jsx
export async function loader({ params }) {
```tsx
export async function loader({ params }: LoaderArgs) {
params["*"]; // "images/work/flyer.jpg"
}
```
Expand Down
18 changes: 10 additions & 8 deletions docs/hooks/use-action-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ title: useActionData

This hook returns the JSON parsed data from your route action. It returns `undefined` if there hasn't been a submission at the current location yet.

```tsx lines=[2,11,20]
```tsx lines=[3,12,21]
import type { ActionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useActionData, Form } from "@remix-run/react";
import { Form, useActionData } from "@remix-run/react";

export async function action({ request }) {
export async function action({ request }: ActionArgs) {
const body = await request.formData();
const name = body.get("visitorsName");
return json({ message: `Hello, ${name}` });
}

export default function Invoices() {
const data = useActionData();
const data = useActionData<typeof action>();
return (
<Form method="post">
<p>
Expand All @@ -34,11 +35,12 @@ export default function Invoices() {

The most common use-case for this hook is form validation errors. If the form isn't right, you can simply return the errors and let the user try again (instead of pushing all the errors into sessions and back out of the loader).

```tsx lines=[22, 31, 39-41, 45-47]
import { redirect, json } from "@remix-run/node"; // or cloudflare/deno
```tsx lines=[23,32,40-42,46-48]
import type { ActionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json, redirect } from "@remix-run/node"; // or cloudflare/deno
import { Form, useActionData } from "@remix-run/react";

export async function action({ request }) {
export async function action({ request }: ActionArgs) {
const form = await request.formData();
const email = form.get("email");
const password = form.get("password");
Expand All @@ -65,7 +67,7 @@ export async function action({ request }) {
}

export default function Signup() {
const errors = useActionData();
const errors = useActionData<typeof action>();

return (
<>
Expand Down
20 changes: 10 additions & 10 deletions docs/hooks/use-fetcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,13 @@ See also:
Perhaps you have a persistent newsletter signup at the bottom of every page on your site. This is not a navigation event, so useFetcher is perfect for the job. First, you create a Resource Route:

```tsx filename=routes/newsletter/subscribe.tsx
export async function action({ request }) {
export async function action({ request }: ActionArgs) {
const email = (await request.formData()).get("email");
try {
await subscribe(email);
return json({ ok: true });
return json({ error: null, ok: true });
} catch (error) {
return json({ error: error.message });
return json({ error: error.message, ok: false });
}
}
```
Expand Down Expand Up @@ -243,12 +243,12 @@ Because `useFetcher` doesn't cause a navigation, it won't automatically work if
If you want to support a no JavaScript experience, just export a component from the route with the action.

```tsx filename=routes/newsletter/subscribe.tsx
export async function action({ request }) {
export async function action({ request }: ActionArgs) {
// just like before
}

export default function NewsletterSignupRoute() {
const newsletter = useActionData();
const newsletter = useActionData<typeof action>();
return (
<Form method="post" action="/newsletter/subscribe">
<p>
Expand Down Expand Up @@ -306,7 +306,7 @@ import { Form } from "@remix-run/react";
import { NewsletterForm } from "~/NewsletterSignup";

export default function NewsletterSignupRoute() {
const data = useActionData();
const data = useActionData<typeof action>();
return (
<NewsletterForm
Form={Form}
Expand Down Expand Up @@ -343,14 +343,14 @@ function useMarkAsRead({ articleId, userId }) {
Anytime you show the user avatar, you could put a hover effect that fetches data from a loader and displays it in a popup.

```tsx filename=routes/user/$id/details.tsx
export async function loader({ params }) {
export async function loader({ params }: LoaderArgs) {
return json(
await fakeDb.user.find({ where: { id: params.id } })
);
}

function UserAvatar({ partialUser }) {
const userDetails = useFetcher();
const userDetails = useFetcher<typeof loader>();
const [showDetails, setShowDetails] = useState(false);

useEffect(() => {
Expand Down Expand Up @@ -382,15 +382,15 @@ function UserAvatar({ partialUser }) {
If the user needs to select a city, you could have a loader that returns a list of cities based on a query and plug it into a Reach UI combobox:

```tsx filename=routes/city-search.tsx
export async function loader({ request }) {
export async function loader({ request }: LoaderArgs) {
const url = new URL(request.url);
return json(
await searchCities(url.searchParams.get("city-query"))
);
}

function CitySearchCombobox() {
const cities = useFetcher();
const cities = useFetcher<typeof loader>();

return (
<cities.Form method="get" action="/city-search">
Expand Down
2 changes: 1 addition & 1 deletion docs/hooks/use-loader-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function loader() {
}

export default function Invoices() {
const invoices = useLoaderData();
const invoices = useLoaderData<typeof loader>();
// ...
}
```
5 changes: 3 additions & 2 deletions docs/hooks/use-submit.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ Returns the function that may be used to submit a `<form>` (or some raw `FormDat

This is useful whenever you need to programmatically submit a form. For example, you may wish to save a user preferences form whenever any field changes.

```tsx filename=app/routes/prefs.tsx lines=[2,14,18]
```tsx filename=app/routes/prefs.tsx lines=[3,15,19]
import type { ActionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useSubmit, useTransition } from "@remix-run/react";

export async function loader() {
return json(await getUserPreferences());
}

export async function action({ request }) {
export async function action({ request }: ActionArgs) {
await updatePreferences(await request.formData());
return redirect("/prefs");
}
Expand Down
7 changes: 4 additions & 3 deletions docs/route/action.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ Actions have the same API as loaders, the only difference is when they are calle
This enables you to co-locate everything about a data set in a single route module: the data read, the component that renders the data, and the data writes:

```tsx
import type { ActionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json, redirect } from "@remix-run/node"; // or cloudflare/deno
import { Form } from "@remix-run/react";

import { fakeGetTodos, fakeCreateTodo } from "~/utils/db";
import { TodoList } from "~/components/TodoList";
import { fakeCreateTodo, fakeGetTodos } from "~/utils/db";

export async function loader() {
return json(await fakeGetTodos());
}

export async function action({ request }) {
export async function action({ request }: ActionArgs) {
const body = await request.formData();
const todo = await fakeCreateTodo({
title: body.get("title"),
Expand All @@ -32,7 +33,7 @@ export async function action({ request }) {
}

export default function Todos() {
const data = useLoaderData();
const data = useLoaderData<typeof loader>();
return (
<div>
<TodoList todos={data} />
Expand Down
Loading

0 comments on commit e1a4469

Please sign in to comment.