Skip to main content

@jmondi/zod-friendly-forms@2.0.0

Validate forms with ease using Zod and get user-friendly error messages or valid typed data, compatible with any framework on both server and client side.

Works with
This package works with Bun
It is unknown whether this package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
This package works with Browsers
JSR Score
100%
Published
2 months ago

Zod Friendly Forms

JSR JSR Score GitHub Workflow Status Test Coverage

Returns an object containing errors and validData. The errors object contains user-friendly error messages, making it easy to display validation errors to the user, while the validData object contains the typed and valid data from the schema. This library can be used in any framework, both on the server or client side and it allows for easy validation and handling of form submissions.

Install (npm)

pnpm add zod
pnpm dlx jsr add @jmondi/zod-friendly-forms

Deno

deno add @jmondi/zod-friendly-forms

Usage

Create a [zod] schema.

import { z } from "zod";
const schema = z.object({
  email: z.string().email(),
});

When you're ready to validate your input data, go ahead and run the parseForm function. If there are any errors, they will be available by input key.

import { parseForm } from "@jmondi/zod-friendly-forms";

const data = {
  email: "invalid-email",
};
const { errors } = parseForm({ schema, data });

errors;
// {
//   email: "Invalid email",
// }

If errors are undefined, the input was valid. A returned validData object will be typed with your response.

import { parseForm } from "@jmondi/zod-friendly-forms";

const data = {
  email: "bob@example.com",
};
const { errors, validData } = parseForm({ schema, data });

errors;
// undefined

validData;
// {
//   email: "bob@example.com",
// }

You can use the builtin FormData or URLSearchParams object.

const data = new FormData();
data.append("email", "invalid-email");

const { errors } = parseForm({ schema, data });

errors;
// {
//   email: "Invalid email",
// }
const schema = z.object({
  user: z.object({
    email: z.string().email(),
  }),
});

const data = {
  user: {
    email: "bob",
  },
};

const { errors } = parseForm({ schema: RegisterSchema, data }, options);

errors;
// {
//   "user.email": "Invalid Email Address",
// }

Examples

This library will work on the server or client, in any framework.

Svelte Examples

<script lang="ts">
  import { z } from "zod";
  import { parseForm } from "@jmondi/zod-friendly-forms";

  import { handleLogin } from "./my-login-function";

  let errors;

  const LoginSchema = z.object({
    email: z.string().email(),  
    password: z.string().min(8),  
  });  
  
  const loginForm = {  
    email: "",  
    password: "",  
  };  
  
  async function submit() {  
    let { data, errors } = await parseForm<typeof LoginSchema>({ schema: LoginSchema, data: loginForm });  
    if (!errors) await handleLogin(data);  
  }  
</script>  
  
<form on:submit|preventDefault="{submit}">  
  <label for="email">Email
    {#if errors?.email}<span class="error">{errors.email}</span>{/if}
    <input
      id="email"
      name="email"
      type="email"
      required="required"
      bind:value="{loginForm.email}"
    />
  </label>

  <label for="password">Password
    {#if errors?.password}<span class="error">{errors.password}</span>{/if}
    <input
      id="password"
      name="password"
      type="password"
      required="required"
      bind:value="{loginForm.password}"
    />
  </label>
  
  <footer class="form-submit">  
    <button type="submit">Submit</button>  
  </footer>  
</form>

React Example

import React, { useState } from "react";
import { z } from "zod";
import { parseForm } from "@jmondi/zod-friendly-forms";
import { handleLogin } from "./my-login-function";

const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  rememberMe: z.boolean(),
});

const LoginForm = () => {
  const [formData, setFormData] = useState({
    email: "",
    password: "",
    rememberMe: false,
  });
  const [errors, setErrors] = useState({});

  const handleSubmit = async (e) => {
    e.preventDefault();
    let { data, errors } = await parseForm({
      schema: LoginSchema,
      data: formData,
    });
    if (!errors) await handleLogin(data);
    setErrors(errors);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="email">
        Email
        {errors.email && <span className="error">{errors.email}</span>}
        <input
          id="email"
          name="email"
          type="email"
          required
          value={formData.email}
          onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        />
      </label>

      <label htmlFor="password">
        Password
        {errors.password && <span className="error">{errors.password}</span>}
        <input
          id="password"
          name="password"
          type="password"
          required
          value={formData.password}
          onChange={(e) =>
            setFormData({ ...formData, password: e.target.value })}
        />
      </label>
      <label htmlFor="rememberMe">
        Remember Me
        <input
          id="rememberMe"
          type="checkbox"
          checked={formData.rememberMe}
          onChange={(e) =>
            setFormData({ ...formData, rememberMe: e.target.checked })}
        />
      </label>

      <footer className="form-submit">
        <button type="submit">Submit</button>
      </footer>
    </form>
  );
};

Vue 3 Example

<template>  
  <form @submit.prevent="submit">  
    <label for="email">Email
      <span class="error" v-if="errors.email">{{ errors.email }}</span>
      <input
        id="email"
        name="email"
        type="email"
        required
        v-model="formData.email"
      />
    </label>

    <label for="password">Password
      <span class="error" v-if="errors.password">{{ errors.password }}</span>
      <input
        id="password"
        name="password"
        type="password"
        required
        v-model="formData.password"
      />
    </label>
    <label for="rememberMe">Remember Me
      <input
        id="rememberMe"
        type="checkbox"
        v-model="formData.rememberMe"
      />
    </label>

    <footer class="form-submit">
      <button type="submit">Submit</button>  
    </footer>  
  </form>  
</template>

<script>
import { z } from 'zod';
import { parseForm } from 'zod-ff';
import { handleLogin } from './my-login-function';

const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  rememberMe: z.boolean(),  
});  
  
export default {  
  data() {  
    return {  
      formData: {  
        email: "",  
        password: "",  
        rememberMe: false,  
      },  
      errors: {}  
    };  
  },  
  methods: {  
    async submit() {  
      let { data, errors } = await parseForm({ schema: LoginSchema, data: this.formData });  
      if (!errors) await handleLogin(data);  
      this.errors = errors;  
    }  
  }  
};  
</script>
Built and signed on
GitHub Actions
View transparency log

Add Package

deno add @jmondi/zod-friendly-forms

Import symbol

import * as mod from "@jmondi/zod-friendly-forms";

Add Package

npx jsr add @jmondi/zod-friendly-forms

Import symbol

import * as mod from "@jmondi/zod-friendly-forms";

Add Package

yarn dlx jsr add @jmondi/zod-friendly-forms

Import symbol

import * as mod from "@jmondi/zod-friendly-forms";

Add Package

pnpm dlx jsr add @jmondi/zod-friendly-forms

Import symbol

import * as mod from "@jmondi/zod-friendly-forms";

Add Package

bunx jsr add @jmondi/zod-friendly-forms

Import symbol

import * as mod from "@jmondi/zod-friendly-forms";