Hi fellow VanJSers,
I'm thrilled to announce: VanX, the official VanJS extension is finally here! ๐๐๐
VanX is a collection of utility functions that enable you to write more ergonomic code for your apps. In this release, it provides 2 primary functionalities:
vanX.reactive: Grouping many VanJS states into a single reactive object
vanX.reactive helps you define a single reactive object that is ideal for holding many State objects for the status of your application. The fields of the reactive objects can be deeply nested. Let's take a look at an example below:
const base = vanX.reactive({ a: 1, b: 2, name: { first: "Tao", last: "Xin", }, list: [1, 2, 3], })
Getting and setting the fields in the reactive object is equivalent to getting and setting the values of its underlying states. With the reactive object, it's easy to define DOM nodes on top of its fields:
const aDom = input({type: "number", min: 1, max: 9, value: () => base.a, oninput: e => base.a = e.target.value}) const bDom = input({type: "number", min: 1, max: 9, value: () => base.b, oninput: e => base.b = e.target.value}) const firstNameDom = input({type: "text", value: () => base.name.first, oninput: e => base.name.first = e.target.value}) const lastNameDom = input({type: "text", value: () => base.name.last, oninput: e => base.name.last = e.target.value})
Note that, not only you can set the value of each individual leaf field, you can also set the entire object of a subfield. For instance, the following code:
base.name = {first: "Tao", last: "Xin"}
is equivalent to:
base.name.first = "Tao" base.name.last = "Xin"
All the bound UI elements are guaranteed to be updated accordingly, no matter which way you set the fields.
You can also specify calculated fields in the reactive object, the value of a calculated field depends on the values of other fields according to a calculation function. For instance, we can have a reactive object with calculated fields like that:
const derived = vanX.reactive({ // Derived individual fields a: { double: vanX.calc(() => base.a * 2), squared: vanX.calc(() => base.a * base.a), }, // Derived object b: vanX.calc(() => ({ double: base.b * 2, squared: base.b * base.b, })), fullName: vanX.calc(() => `${base.name.first} ${base.name.last}`), list: vanX.calc(() => ({ length: base.list.length, sum: base.list.reduce((acc, val) => acc + Number(val), 0), })), })
Calculated fields can be used the same way as other fields.
Preview a sample app via CodeSandbox
You can refer to https://vanjs.org/x#reactive-object for more information about vanX.reactive.
vanX.list: Building a reactive list that minimizes DOM re-rendering when its values are updated
vanX.list is a utility function that helps you build a UI element based on a reactive list of values. It supports both non-keyed data (if the reactive object is an array) or keyed data (if the reactive object is a plain object).
Any change to the reactive list, such as inserting an item, updating an item, or deleting an item will be propagated to the UI elements it's bound to.
With the help of vanX.list, we can re-implement the reactive TODO app with substantial simplification (40+ lines of code => just over 10 lines):
const TodoList = () => { const items = vanX.reactive(JSON.parse(localStorage.getItem("appState") ?? "[]")) van.derive(() => localStorage.setItem("appState", JSON.stringify(items.filter(v => v)))) const inputDom = input({type: "text"}) return div( inputDom, button({onclick: () => items.push({text: inputDom.value, done: false})}, "Add"), vanX.list(div, items, ({val: v}, deleter) => div( input({type: "checkbox", checked: () => v.done, onclick: e => v.done = e.target.checked}), () => (v.done ? strike : span)(v.text), a({onclick: deleter}, "โ"), )), ) }
You might notice how easy it is to serialize/deserialize a complex reactive object into/from external storage. This is indeed one notable benefit of reactive objects provided by vanX.reactive.
Not only you can update the list object one item at a time, we also provide the vanX.replace function that enables you to update, insert, delete and reorder items in batch. Let's take a look at the examples below:
// Assume we have a few TODO items as following: const todoItems = vanX.reactive([ {text: "Implement VanX", done: true}, {text: "Test VanX", done: false}, {text: "Write a tutorial for VanX", done: false}, ]) // To delete items in batch const clearCompleted = () => vanX.replace(todoItems, l => l.filter(v => !v.done)) // To update items in batch const appendText = () => vanX.replace(todoItems, l => l.map(v => ({text: v.text + "!", done: v.done}))) // To reorder items in batch const sortItems = () => vanX.replace(todoItems, l => l.toSorted((a, b) => a.localeCompare(b))) // To insert items in batch const duplicateItems = () => vanX.replace(todoItems, l => l.flatMap(v => [v, {text: v.text + " copy", done: v.done}]))
Preview sample apps where vanX.list and vanX.replace are being used: App 1, App 2.
You can refer to https://vanjs.org/x#reactive-list for more information about vanX.list.
Since the public release of VanJS, we have received enormous feedback from the community. There are common pain points expressed by its users as well as scenarios where programming with VanJS feels different compared to other UI frameworks. VanX is the effort to bridge the gap and address the most prominent pain points of VanJS. I am hoping it can bring the developer experience of VanJS into the next level!
โค๏ธ Hope you can enjoy!