Remix Framework Guide
Loader (Data Fetching)
// app/routes/users.$id.tsx
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import type { LoaderFunctionArgs } from '@remix-run/node';
export async function loader({ params }: LoaderFunctionArgs) {
const user = await getUser(params.id);
if (!user) throw new Response('Not Found', { status: 404 });
return json({ user });
}
export default function UserPage() {
const { user } = useLoaderData<typeof loader>();
return <h1>{user.name}</h1>;
}
Action (Form Handling)
import { Form, useActionData } from '@remix-run/react';
import { redirect } from '@remix-run/node';
import type { ActionFunctionArgs } from '@remix-run/node';
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const name = formData.get('name') as string;
if (!name) return json({ error: 'Name required' }, { status: 400 });
await createUser({ name });
return redirect('/users');
}
export default function NewUser() {
const data = useActionData<typeof action>();
return (
<Form method="post">
<input name="name" type="text" />
{data?.error && <p>{data.error}</p>}
<button type="submit">Create</button>
</Form>
);
}
File-based Routing
app/routes/
โโโ _index.tsx # /
โโโ about.tsx # /about
โโโ users._index.tsx # /users
โโโ users.$id.tsx # /users/:id
โโโ users.$id.edit.tsx # /users/:id/edit
โโโ _layout.tsx # shared layout
Error Boundaries
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <div>{error.status} {error.statusText}</div>;
}
return <div>Something went wrong</div>;
}