Skip to content

chungweileong94/server-act

Repository files navigation

Server-Act

npm version

A simple React server action builder that provides input validation with zod.

Installation

# npm
npm install server-act zod

# yarn
yarn add server-act zod

# pnpm
pnpm add server-act zod

Usage

// action.ts
"use server";

import { serverAct } from "server-act";
import { z } from "zod";

export const sayHelloAction = serverAct
  .input(
    z.object({
      name: z.string(),
    }),
  )
  .action(async ({ input }) => {
    return `Hello, ${input.name}`;
  });
// client-component.tsx
"use client";

import { sayHelloAction } from "./action";

export const ClientComponent = () => {
  const onClick = () => {
    const message = await sayHelloAction({ name: "John" });
    console.log(message); // Hello, John
  };

  return (
    <div>
      <button onClick={onClick}>Trigger action</button>
    </div>
  );
};

With Middleware

// action.ts
"use server";

import { serverAct } from "server-act";
import { z } from "zod";

export const sayHelloAction = serverAct
  .middleware(() => {
    const userId = "...";
    return { userId };
  })
  .input(
    z.object({
      name: z.string(),
    }),
  )
  .action(async ({ ctx, input }) => {
    console.log("User ID", ctx.userId);
    return `Hello, ${input.name}`;
  });

useActionState Support

useActionState Documentation:

We recommend using zod-form-data for input validation.

// action.ts;
"use server";

import { serverAct } from "server-act";
import { z } from "zod";
import { zfd } from "zod-form-data";

export const sayHelloAction = serverAct
  .input(
    zfd.formData({
      name: zfd.text(
        z
          .string({ required_error: `You haven't told me your name` })
          .max(20, { message: "Any shorter name? You name is too long 😬" }),
      ),
    }),
  )
  .formAction(async ({ formData, input, formErrors, ctx }) => {
    if (formErrors) {
      return { formData, formErrors: formErrors.formErrors.fieldErrors };
    }
    return { message: `Hello, ${input.name}!` };
  });
// client-component.tsx
"use client";

import { useActionState } from "react";
import { sayHelloAction } from "./action";

export const ClientComponent = () => {
  const [state, dispatch] = useFormState(sayHelloAction, undefined);

  return (
    <form action={dispatch}>
      <input
        name="name"
        required
        defaultValue={state?.formData?.get("name")?.toString()}
      />
      {state?.formErrors?.name?.map((error) => <p key={error}>{error}</p>)}

      <button type="submit">Submit</button>

      {!!state?.message && <p>{state.message}</p>}
    </form>
  );
};