• Joined on 2025-01-05

@plat/remix-conform-rpc (4.1.3)

Published 2025-02-20 19:24:29 +00:00 by plat

Installation

@plat:registry=
npm install @plat/remix-conform-rpc@4.1.3
"@plat/remix-conform-rpc": "4.1.3"

About this package

Remix conform RPC

Supercharge your remix loaders and actions with conform and zod.

Credits

The API and design are heavily inspired by remix-easy-mode by Samuel Cook. You can find his repo here: https://github.com/sjc5/remix-easy-mode

Special thanks to Kiliman for providing the utilities for param and query parsing: remix-params-helper by kiliman

Form data parsing is done using conform by edmundhung

Installation

Install the package and required peer dependencies

npm

npm install remix-conform-rpc zod remix-params-helper @conform-to/react @conform-to/zod @conform-to/dom

yarn

yarn add remix-conform-rpc zod remix-params-helper @conform-to/react @conform-to/zod @conform-to/dom

Defining loaders

Defining a simple loader

You can define a loader by calling the setupLoader function and passing an object with a load function.

import { setupLoader } from "remix-conform-rpc/server/loader";

export const loader = (loaderArgs: LoaderFunctionArgs) => setupLoader({
  loaderArgs,
  load: async ({ context, request }) => {
    return { message: "hello world" };
  }
});

Parsing params and path queries

You can add type-safe query and param parsing by using the paramSchema and/or querySchema props. Once you define a param or query schema, the object becomes available in the params and query object in the load function.

import { setupLoader } from "remix-conform-rpc/server/loader";
import { z } from "zod";

export const loader = (loaderArgs: LoaderFunctionArgs) => setupLoader({
  loaderArgs,
  querySchema: z.object({
    page: z.coerce.number().optional()
  }),
  paramSchema: z.object({
    id: z.string()
  }),
  load: async ({ context, request, params, query }) => {
    params.id; // string - typesafe
    query.page; // number | undefined - typesafe

    return { message: "hello world" };
  }
});

Running middleware

You can run middleware before your loader. Anything you return from your middleware will be available in the load functions arguments.

import { setupLoader } from "remix-conform-rpc/server/loader";
import { z } from "zod";

export const loader = (loaderArgs: LoaderFunctionArgs) => setupLoader({
  loaderArgs,
  middleware: async ({ context, request }) => {
    const user = await getUserFromSession(request);
    return { user };
  },
  load: async ({ context, request, user }) => {
    user; // user object returned from middleware
    return { message: "hello world" };
  }
});

Defining actions

Defining a simple action with a zod schema

Define a loader with a zod schema to parse and validate the form data body.

import { setupAction } from "remix-conform-rpc/server/action";
import { z } from "zod";

export const action = (actionArgs: ActionFunctionArgs) => setupAction({
  actionArgs,
  schema: z.object({
    email: z.string().email(),
    password: z.string().min(8)
  }),
  mutation: async ({ request, submission }) => {
    //Already validated and parsed
    const { email, password } = submission.value;
  }
});

Note

If the submission validation fails, the following object will be returned from your action (with http status 400):

{
  "error": "invalid_submission",
  "status": "error",
  "code": 400,
  "result": {}
  //conform submission reply with errors
}

Params, Query and Middleware

The same way you can define loaders, you can define actions with params, query and middleware.

import { setupAction } from "remix-conform-rpc/server/action";
import { z } from "zod";

export const action = (actionArgs: ActionFunctionArgs) => setupAction({
  actionArgs,
  schema: z.object({
    email: z.string().email(),
    password: z.string().min(8)
  }),
  querySchema: z.object({
    page: z.coerce.number().optional()
  }),
  paramSchema: z.object({
    id: z.string()
  }),
  middleware: async ({ context, request, params }) => {
    const user = await getUserFromSession(request);
    await checkUserPermissions(user, params.id);
    return { user };
  },
  mutation: async ({ request, submission, user, query, params }) => {
    return { message: "hello world" };
  }
});

Consuming client-side

While you can use standard html forms to submit data, you can also enhance your users experience with the useAction hook.

import { useAction } from "remix-conform-rpc/hooks/action";
import { z } from "zod";


const formSchema = z.object({
  name: z.string(),
  description: z.string().optional()
});

const { submit, fetcher } = useAction<typeof action, typeof formSchema>({
  //all options are optional
  path: "/api/products",
  method: "post",
  onSuccess: (actionResult) => {
    //do something with the result
  },
  onError: (errorResult) => {
    const data = errorResult.result; //Return from the server
    const status = errorResult.status; //"error"
    const statusCode = errorResult.code; // http status code
    const errorMessage = errorResult.error; // error message from the server
  }
});

//Parameters and types are automatically inferred
submit({
  name: "Product name",
  description: "Product description"
});

Auto-creating form data with conform

You can also leverage typesafe form creating using the useActionForm hook.

import { useActionForm } from "remix-conform-rpc/hooks/action";
import { z } from "zod";

const formSchema = z.object({
  name: z.string(),
  description: z.string().optional()
});

const { form, fields, submit, fetcher } = useActionForm<typeof action, typeof formSchema>({
  //All options are optional
  onSuccess: (actionResult) => {
    //do something with the result
  },
  onError: (errorResult) => {
    const data = errorResult.result; //Return from the server
    const status = errorResult.status; //"error"
    const statusCode = errorResult.code; // http status code
    const errorMessage = errorResult.error; // error message from the server
  },
  onSubmit: (event, { name, description }) => {
    event.preventDefault();
    submit({ name, description });
  },
  defaultValue: {
    name: "My product",
    description: "My product description"
  }
});

See the conform documentation for more information on how to use the form and fields objects

Dependencies

Development Dependencies

ID Version
@arethetypeswrong/cli ^0.17.1
@conform-to/dom ^1.2.2
@conform-to/react ^1.2.2
@conform-to/zod ^1.2.2
@testing-library/dom ^10.4.0
@testing-library/react ^16.1.0
@total-typescript/tsconfig ^1.0.4
@types/react ^19
@types/react-dom ^19
eslint ^8.52.0
jsdom ^25.0.1
prettier ^3.4.2
react ^18.3.0
react-dom ^18.3.0
react-router ^7.0.0
remix-params-helper ^0.5.1
typescript ^5.7.3
vitest ^3.0.4
zod ^3.24.1

Peer Dependencies

ID Version
@conform-to/dom >=1.2.2
@conform-to/react >=1.2.2
@conform-to/zod >=1.2.2
react-router >=7.0.0
remix-params-helper >=0.5.1
zod >=3

Keywords

remix typescript typesafe zod api react query mutation action loader forms validation form validation trpc bouncer session middleware conform
Details
npm
2025-02-20 19:24:29 +00:00
3
Tim Zolleis
MIT
2.7 KiB
Assets (1)
Versions (2) View all
4.1.4 2025-02-20
4.1.3 2025-02-20