Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/miroslavpetrik/react-form-action
tRPC like builder for the Next.js & React form actions.
https://github.com/miroslavpetrik/react-form-action
next-form-action nextjs react react-form-action server-action typescript
Last synced: 17 days ago
JSON representation
tRPC like builder for the Next.js & React form actions.
- Host: GitHub
- URL: https://github.com/miroslavpetrik/react-form-action
- Owner: MiroslavPetrik
- License: mit
- Created: 2023-11-12T18:54:46.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2024-09-25T16:39:59.000Z (about 2 months ago)
- Last Synced: 2024-10-24T17:56:55.200Z (26 days ago)
- Topics: next-form-action, nextjs, react, react-form-action, server-action, typescript
- Language: TypeScript
- Homepage:
- Size: 172 KB
- Stars: 19
- Watchers: 1
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# react-form-action
End-to-end typesafe success, error & validation state control for Next.js 14 form actions.
## Features
**Action Creator**
- ✅ Provides `"invalid" | "success" | "failure"` response objects.
- ✅ Define generic payload for each of the response type.**Form Action builder**
- ✅ tRPC-like builder API for `.input(zodSchema)` & context `.use(middleware)`
- ✅ Parses `formData` with [`zod-form-data`](https://www.npmjs.com/package/zod-form-data)**Stateful ``**
- ✅ `` component reads the action's response.
- ✅ Computes progress meta-state like `isInvalid`, `isSuccess` and more.## Install
```
npm i react-form-action zod-form-data
```## Usage
### `formAction` builder
```ts
// app/actions/auth.ts
"use server";import { formAction } from "react-form-action";
import { z } from "zod";
import { cookies } from "next/headers";const i18nMiddleware = async () => {
const { t } = await useTranslation("auth", cookies().get("i18n")?.value);
// will be added to context
return { t };
};const authAction = formAction
.use(i18nMiddleware)
.use(async ({ ctx: { t } }) =>
console.log("🎉 context enhanced by previous middlewares 🎉", t),
)
.error(({ error }) => {
if (error instanceof DbError) {
return error.custom.code;
} else {
// unknown error
// default Next.js error handling (error.js boundary)
throw error;
}
});export const signIn = authAction
.input(z.object({ email: z.string().email() }))
// 🎉 extend the previous input (only without refinements and transforms)
.input(z.object({ password: z.string() }))
.run(async ({ ctx: { t }, input: { email, password } }) => {
// Type inferred: {email: string, password: string}await db.signIn({ email, password });
return t("verificationEmail.success");
});export const signUp = authAction
.input(
z
.object({
email: z.string().email(),
password: z.string(),
confirm: z.string(),
})
.refine((data) => data.password === data.confirm, {
message: "Passwords don't match",
path: ["confirm"],
}),
) // if using refinement, only one input call is permited, as schema with ZodEffects is not extendable.
.run(async ({ ctx: { t }, input: { email, password } }) => {
// 🎉 passwords match!const tokenData = await db.signUp({ email, password });
if (!tokenData) {
return t("signUp.emailVerificationRequired");
}return t("singUp.success");
});
```### Server Action (usable in Next.js)
```ts
"use server";import { createFormAction } from "react-form-action";
import { z } from "zod";// Define custom serializable error & success data types
type ErrorData = {
message: string;
};type SuccessData = {
message: string;
};type ValiationError = {
name?: string;
};const updateUserSchema = z.object({ name: z.string() });
export const updateUser = createFormAction<
SuccessData,
ErrorData,
ValiationError
>(({ success, failure, invalid }) =>
// success and failure helper functions create wrappers for success & error data respectively
async (prevState, formData) => {
if (prevState.type === "initial") {
// use the initialData passed to here
// prevState.data === "foobar"
}try {
const { name } = updateUserSchema.parse({
name: formData.get("name"),
});const user = await updateCurrentUser(name);
if (user) {
// {type: "success", data: "Your profile has been updated.", error: null, validationError: null}
return success({
message: "Your profile has been updated.",
});
} else {
// {type: "error", data: null, error: { message: "No current user." }, validationError: null}
return failure({ message: "No current user." });
}
} catch (error) {
if (error instanceof ZodError) {
// {type: "invalid", data: null, error: null, validationError: {name: "Invalid input"}}
return invalid({
name: error.issues[0]?.message ?? "Validation error",
});
}return failure({ message: "Failed to update user." });
}
},
);
```### `` Component
```tsx
"use client";
// Form connects the formAction to the formState & provides the meta state props via render prop
import { Form } from "react-form-action/client";import { updateUser } from "@/actions";
export function UpdateUserForm() {
return (
{({
error,
data,
validationError,
isPending,
isFailure,
isInvalid,
isSuccess,
isInitial,
}) => (
<>
{/* safely access the data or error by checking the mutually exclusive boolean flags: */}
{isSuccess &&{data}
}
{isFailure &&{error.message}
}
{isInvalid && (
{validationError.name}
)}
{isPending ? "Submitting..." : "Submit"}
>
)}
);
}
```### Bonus ``
The `useFormStatus` hook data exposed via render prop:
```tsx
import { FormStatus } from "react-form-action/client";// alleviates the need to create a separate component using the useFormStatus hook
return function MyForm() {
return (
{({ pending }) => (
{pending ? "Submitting..." : "Submit"}
)}
);
};
```