Skip to content

Fully typed form state management library (with validation) for VanJS

Notifications You must be signed in to change notification settings

kwameopareasiedu/vanjs-form

Repository files navigation

VanJS Form

Fully typed form state management library (with validation) for the VanJS framework. If you are coming from React, vanjs-form feels similar to react-hook-form.

Install

yarn add vanjs-form vanjs-core yup
npm i -S vanjs-form vanjs-core yup

Features

  1. Dead simple Form API to create and manage the form state
  2. Fully typed method parameter and return types
  3. Supports validation with Yup. Zod and Joi coming soon!
  4. Built with developer experience (DX) in mind

QuickStart

import van from "vanjs-core";
import { Form, yupValidator } from "vanjs-form";
import * as yup from "yup";

const { div, h1, p, input, label, select, form: formEl, button, br } = van.tags;

function App() {
  const form = new Form({
    initialValues: {
      title: "",
      name: "",
      age: 0,
      gender: ""
    },
    validator: yupValidator(
      yup.object({
        title: yup.string().required("Required"),
        name: yup.string().required("Required"),
        age: yup.number().required("Required").min(18, "At least 18 years"),
        gender: yup.string().oneOf(["Male", "Female"], "Valid gender required")
      })
    ),
    validationMode: "oninput" // Validation is run on input as well as on submit. Defaults to 'onsubmit'
  });

  // Watch values when they change in the form fields
  const observables = form.watch("name", "title");
  // or
  const allValues = form.watch();

  // Called when form passes validation
  const onsubmit = form.handleSubmit((values) => {
    console.log({ submittedValues: values });
  });

  return div(
    h1(() => `VanJS Form for ${observables.val.title} ${observables.val.name}`),

    formEl(
      { onsubmit: onsubmit },
      label("Title"),
      div(
        label(
          input(form.register("title", { type: "radio", value: "Mr." })),
          "Mr."
        ),
        label(
          input(form.register("title", { type: "radio", value: "Ms." })),
          "Ms."
        ),
        label(
          input(form.register("title", { type: "radio", value: "Mrs." })),
          "Mrs."
        )
      ),

      br(),

      label("Name"),
      input(form.register("name", { autofocus: true })),

      br(),

      label("Age"),
      input(form.register("age", { type: "number", min: "0", max: "100" })),

      br(),

      label("Gender"),
      select(
        form.register("gender", { type: "number", min: "0", max: "100" }),
        option({ value: "" }, "Select..."),
        option({ value: "Male" }, "Male"),
        option({ value: "Female" }, "Female")
      ),

      br(),

      button({ type: "submit" }, "Submit")
    )
  );
}

van.add(document.body, App());

API Reference

Form

  • The Form class controls the form state and has methods to manipulate this state
  • If validator parameter is provided, the submit handler is called if validation passes
  • Use validationMode to control whether validation should occur on input or only on submit
import { Form } from "vanjs-form";

const form = new Form({
  initialValues: Record<string, any>,
  validator: (values: Record<string, any>) => Record<string, any> | FormError,
  validationMode: "onsubmit" | "oninput" // Defaults to onsubmit
});

After the instance is created, you can access the form functions to work with your form.

YupValidator

  • The yupValidator function takes a Yup ObjectSchema and returns a validator function

Zod and Joi validators coming soon!

const form = new Form({
  initialValues: {
    title: "",
    name: "",
    age: 0,
    gender: ""
  },
  validator: yupValidator(
    yup.object({
      title: yup.string().required("Required"),
      name: yup.string().required("Required"),
      age: yup.number().required("Required").min(18, "At least 18 years"),
      gender: yup.string().oneOf(["Male", "Female"], "Valid gender required")
    })
  )
});

Form methods

import { Form } from "vanjs-form";

const form = new Form({
  initialValues: Record<string, any>,
  validator: (values: Record<string, any>) => Record<string, any> | FormError,
  validationMode: "onsubmit" | "oninput" // Defaults to onsubmit
});

form.register();
form.get();
form.set();
form.error();
form.watch();
form.reset();
form.handleSubmit();

Register

  • Registers an input with the form
  • Merges additionalProps into returned object
  • Returns an object of props (name, value, checked, oninput, onfocus) which should be assigned as the element's props
const form = new Form({
  initialValues: {
    name: "Kwame",
    age: 28,
    foo: true,
    location: ""
  }
});

return div(
  input(form.register("name", { type: "text", placeholder: "Enter your name" })),
  input(form.register("age", { type: "number", min: 0, max: 100 })),
  input(form.register("foo", { type: "checkbox" })),
  select(
    form.register("select"),
    option({ value: "home" }, "Home"),
    option({ value: "work" }, "Work")
  )
);

Get

  • Gets the typed value of the form field with the name key
const form = new Form({
  initialValues: {
    name: "Kwame",
    age: 28,
    foo: true
  }
});

const name = form.get("name");
console.log(name); // "Kwame"

const age = form.get("age");
console.log(age); // 28

const foo = form.get("foo");
console.log(foo); // true

Set

  • Sets the value of the form field with the name key
const form = new Form({
  initialValues: {
    name: "Kwame",
    age: 28,
    foo: true
  }
});

form.set("name", "Opare");
form.set("age", 29);
form.set("foo", false);

Error

  • Gets the error string of the form field with the name key
const form = new Form({
  initialValues: {
    name: "Kwame",
    age: 28,
    foo: true
  },
  validator: yupValidator(
    yup.object({
      name: yup.string().required("Required").min(6, "Must be more than 6 characters"),
      age: yup.number().required("Required"),
      foo: yup.boolean().required("Required"),
    })
  ),
  validationMode: "oninput"
});

const nameError = form.error("name");
console.log(nameError); // "Must be more than 6 characters"

const ageError = form.error("age");
console.log(ageError); // ""

const fooError = form.error("foo");
console.log(fooError); // ""

An empty string is returned if the field has no error

Watch

  • Returns a reactive object containing the specified field values
  • No need to wrap in van.derive() 🙂
const form = new Form({
  initialValues: {
    name: "Kwame",
    age: 28,
    foo: true
  },
});

const full = form.watch();
const partial = form.watch("name", "age");

return div(
  p(() => `Full data: ${JSON.stringify(full)}`), // Renders: Full data { "name": "Kwame", "age": 28, "foo": true }
  p(() => `Partial data: ${JSON.stringify(partial)}`), // Renders: Partial data { "name": "Kwame", "age": 28 }
);

Errors

  • Returns a reactive object containing the specified field errors
  • No need to wrap in van.derive() 🙂
const form = new Form({
  initialValues: {
    name: "Kwame",
    age: 28,
    foo: true
  },
  validator: yupValidator(
    yup.object({
      name: yup.string().required("Required").min(6, "Must be more than 6 characters"),
      age: yup.number().required("Required"),
      foo: yup.boolean().required("Required"),
    })
  ),
  validationMode: "oninput"
});

const errors = form.errors();
const partial = form.errors("name", "age");

return div(
  p(() => `Full errors: ${JSON.stringify(errors)}`), // Renders: Full errors { "name": "Must be more than 6 characters", "age": "", "foo": "" }
  p(() => `Partial errors: ${JSON.stringify(partial)}`), // Renders: Partial errors { "name": "Must be more than 6 characters", "age": "" }
);

Reset

  • Resets a form field or the entire form
const form = new Form({
  initialValues: {
    name: "Kwame",
    age: 28,
    foo: true
  }
});

form.reset("name"); // Reset name field
form.reset("name", "age"); // Reset name and fields
form.reset(); // Reset all fields

Handle submit

  • Register a function to use as a submit handler
  • If validator is specified on the form, the handler is called if validation passes
  • Returns a new function to be passed to the form element
const form = new Form({
  initialValues: {
    name: "Kwame",
    age: 28,
    foo: true
  }
});

const handleSubmit = form.handleSubmit((values) => {
  console.log(values);
});

return form(
  { onsubmit: handleSubmit },
  // ... Form children
)

Contributors

Changelog

  • 1.1.0 (Current)
    • Added Form.errors() method
  • 1.0.2
    • Disabled call to form.validateField on form.reset to avoid errors on form reset

About

Fully typed form state management library (with validation) for VanJS

Resources

Stars

Watchers

Forks

Packages

No packages published