Fully typed form state management library (with validation) for VanJS
npm install vanjs-formFully 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.


``shell`
yarn add vanjs-form vanjs-core yup
`shell`
npm i -S vanjs-form vanjs-core yup
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
`typescript
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 statevalidator
- If parameter is provided, the submit handler is called if validation passesvalidationMode
- Use to control whether validation should occur on input or only on submit
`typescript
import { Form } from "vanjs-form";
const form = new Form({
initialValues: Record
validator: (values: Record
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 Yup ObjectSchema and returns a validator function
> _Zod and Joi validators coming soon!_
`typescript`
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")
})
)
});
`typescript
import { Form } from "vanjs-form";
const form = new Form({
initialValues: Record
validator: (values: Record
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 objectname
- Returns an object of props (, value, checked, oninput, onfocus) which should be assigned as the
element's props
`typescript
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
`typescript
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
`typescript
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
`typescript
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() 🙂
`typescript
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 }Partial data: ${JSON.stringify(partial)}
p(() => ), // Renders: Partial data { "name": "Kwame", "age": 28 }`
);
- Returns a reactive object containing the specified field errors
- No need to wrap in van.derive() 🙂
`typescript
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": "" }Partial errors: ${JSON.stringify(partial)}
p(() => ), // Renders: Partial errors { "name": "Must be more than 6 characters", "age": "" }`
);
- Resets a form field or the entire form
`typescript
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
`typescript
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() methodform.validateField
- 1.0.2
- Disabled call to on form.reset` to avoid errors on form reset