Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: fix useActionData/useLoaderData usage #4835

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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