ozef v0.1.33
Opinionated Zod-empowered forms
Introduction
Ozef is an opinionated library that aims to guarantee type-safe, declarative forms with minimal boilerplate. It is built on top of Zod which provides a powerful type system for validating data.
Features Ozef supports:
- Guaranteed type-safety in
onSubmit
- async
onSubmit
- Declarative forms
- Validation checking
- Input types like radio and select
Ozef is not a component library. It does not provide any pre-built components. Instead, plug in existing components to build forms.
Ozef lets you build forms like:
import Input from "./CustomInput";
const NewFlowForm = ozef({
schema: z.object({
name: z.string().min(3),
email: z.string().email(),
// for radios
favoriteColor: z.enum(["Red", "Blue"]),
// for selects
favoriteColor: z.union([z.literal("Red"), z.literal("Blue")]),
}),
// Plug in your own input components
Input,
// Define components for each type
InputRadio: ({ radioValue, ...props }) => (
<div>
{radioValue}
<input type="radio" {...props} />
</div>
),
// Error labels
Error: ({ error }) => <span className="text-red-500">{error}</span>,
Submit: ({ submitting }) => (
<RoundedButton type="submit" loading={submitting}>
Submit
</RoundedButton>
),
});
<NewFlowForm
className="flex flex-col gap-2"
onSubmit={async (vals) => {
/** vals has the type { name: string, email: string, favoriteColor: "Red" | "Blue" } */
...
}}
>
<NewFlowForm.Field.Name prefixIcon="icon_1" />
<NewFlowForm.Error.Name />
<NewFlowForm.Field.Email />
<NewFlowForm.Error.Email />
<NewFlowForm.Error.FavoriteColor />
<NewFlowForm.Field.FavoriteColor>
<NewFlowForm.Field.FavoriteColor.Blue />
<NewFlowForm.Field.FavoriteColor.Red />
</NewFlowForm.Field.FavoriteColor>
<NewFlowForm.Event.Submit />
<NewFlowForm.Error.Submission error="Please fill out the form" />
</NewFlowForm>
with full type-script support!
Installation
Ozef has minimal dependencies (just Zod and Jotai) and is easy to install.
npm i zod@3.21.4 jotai ozef
Usage
Basic usage
import ozef from "ozef";
const Form = ozef({
schema: z.object({
name: z.string().min(3),
email: z.string().email(),
}),
});
const SomeComponent = () => {
return (
<Form
onSubmit={async (vals) => {
// vals is guaranteed to be of type { name: string, email: string }
...
}}
>
// Use `Field` components to render inputs for the form
<Form.Field.Name />
<Form.Field.Email />
// Use `Error` components to render error labels
<Form.Error.Name />
// Use `Event` components to render special user events components
<Form.Event.Submit />
</Form>
);
};
Ozef Input Components
Components need to modified before being able to be used with Ozef. This is because Ozef needs to be able to pass certain props to the components.
import { type OzefInputProps } from "ozef";
type InputProps = OzefInputProps & {
// Add your own props
prefixIcon?: string;
};
const Input = ({ prefixIcon, hasError, ...props }: InputProps) => {
return (
<div
className={`${
props.className
} ${hasError ? "focus-within:ring-red-500" : ""}`}
>
{prefixIcon && (
<MaterialsIcon className="!text-xl text-zinc-500" icon={prefixIcon} />
)}
<input
{/* This is the important part. Ozef needs to pass props to the native input component. */}
{...props}
className="..."
/>
</div>
);
};
Defaults
import ozef from "ozef";
const Form = ozef({
schema: z.object({
name: z.string().min(3),
email: z.string().email(),
}),
defaults: {
name: "John Doe",
email: "john-doe@gmail.com"
}
});
const SomeComponent = () => {
return (
<Form
onSubmit={async (vals) => {
// vals is guaranteed to be of type { name: string, email: string }
...
}}
>
// Use `Field` components to render inputs for the form
<Form.Field.Name />
<Form.Field.Email />
// Use `Error` components to render error labels
<Form.Error.Name />
// Use `Event` components to render special user events components
<Form.Event.Submit />
</Form>
);
};
Variable defaults
If you're passing down a variable as defaults, you'll need to use useMemo
to prevent the form from re-rendering every time the parent component re-renders.
import ozef from "ozef";
import { useMemo } from "react";
const SomeComponent = ({defaults}: Props) => {
const Form = useMemo(() => ozef({
schema: z.object({
name: z.string().min(3),
email: z.string().email(),
}),
defaults
}), [defaults])
return (
<Form
onSubmit={async (vals) => {
// vals is guaranteed to be of type { name: string, email: string }
...
}}
>
// Use `Field` components to render inputs for the form
<Form.Field.Name />
<Form.Field.Email />
// Use `Error` components to render error labels
<Form.Error.Name />
// Use `Event` components to render special user events components
<Form.Event.Submit />
</Form>
);
};
shadcn/Radix Select example
import ozef from "ozef";
const UpdateRewriteSettingsForm = ozef({
schema: z.object({
terseness: z.enum(["short", "medium", "long"]),
}),
});
const ShadcnExample = () => {
// getter hook
const terseness = UpdateRewriteSettingsForm.Field.Terseness.useValue();
return (
<UpdateRewriteSettingsForm
onSubmit={() => {
// handler
}}
>
<Select
value={terseness}
// setter function
onValueChange={UpdateRewriteSettingsForm.Field.Terseness.setValue}
>
<div className="space-y-1">
<Label>Terseness</Label>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<UpdateRewriteSettingsForm.Field.Terseness.Short />
<UpdateRewriteSettingsForm.Field.Terseness.Medium />
<UpdateRewriteSettingsForm.Field.Terseness.Long />
</SelectContent>
</div>
</Select>
</UpdateRewriteSettingsForm>
);
};
15 days ago
5 months ago
5 months ago
5 months ago
5 months ago
7 months ago
7 months ago
7 months ago
6 months ago
6 months ago
7 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago