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.
yarn add vanjs-form vanjs-core yup
npm i -S vanjs-form vanjs-core yup
- Dead simple
Form
API to create and manage the form state - Fully typed method parameter and return types
- Supports validation with Yup. Zod and Joi coming soon!
- Built with developer experience (DX) in mind
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());
- 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.
- The
yupValidator
function takes a YupObjectSchema
and returns avalidator
function
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")
})
)
});
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();
- 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")
)
);
- 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
- 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);
- 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
- 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 }
);
- 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": "" }
);
- 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
- 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
)
- 1.1.0 (Current)
- Added
Form.errors()
method
- Added
- 1.0.2
- Disabled call to
form.validateField
onform.reset
to avoid errors on form reset
- Disabled call to