DEV Community

Cover image for The Underrated LemonadeJS Form-to-JSON Sync Feature
Paul H
Paul H

Posted on

The Underrated LemonadeJS Form-to-JSON Sync Feature

LemonadeJS v5 introduced setPath — a bidirectional form-to-JSON binding that most developers haven't discovered yet. No event handlers, no controlled components. Just declare the path and it syncs.

The Problem

You have a JSON object. You have a form. Keeping them in sync means writing event listeners for every field, handling different input types, managing nested objects... it adds up fast.

The Solution

let [data, setData, getData] = setPath(initialObject);
Enter fullscreen mode Exit fullscreen mode
<input lm-path="user.name" />
<input lm-path="user.settings.theme" />
Enter fullscreen mode Exit fullscreen mode

That's it. Form changes update data. Call setData(obj) to populate the form. Call getData() to extract current values.

Complete Example

One component showing everything: defaults, nested binding, load, save, validation callback, and reset.

const defaults = {
    name: '',
    email: '',
    settings: {
        notifications: true,
        theme: 'light'
    }
};

export default function UserForm(children, { setPath, onload }) {
    const self = this;

    // Initialize with optional change callback
    let [data, setData, getData] = setPath(defaults, (value, path, el) => {
        // Validation example
        if (path === 'email' && value && !value.includes('@')) {
            el.classList.add('invalid');
        } else {
            el.classList.remove('invalid');
        }
    });

    // Load existing data
    onload(async () => {
        const id = getRouteId();
        if (id) {
            const user = await fetch(`/api/users/${id}`).then(r => r.json());
            setData(user); // Populates entire form
        }
    });

    const save = async () => {
        const payload = getData(); // Gets entire form as object
        await fetch('/api/users', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });
    };

    const reset = () => setData(defaults);

    return render => render`
        <form>
            <input type="text" lm-path="name" placeholder="Name" />
            <input type="email" lm-path="email" placeholder="Email" />

            <label>
                <input type="checkbox" lm-path="settings.notifications" />
                Email notifications
            </label>

            <select lm-path="settings.theme">
                <option value="light">Light</option>
                <option value="dark">Dark</option>
            </select>

            <button type="button" onclick="${reset}">Reset</button>
            <button type="button" onclick="${save}">Save</button>
        </form>
    `;
}
Enter fullscreen mode Exit fullscreen mode

API Reference

let [data, setData, getData] = setPath(initialObject, onChange);
Enter fullscreen mode Exit fullscreen mode
Return Description
data Reactive object synced with form
setData(obj) JSON → Form
getData() Form → JSON

Callback signature:

(value, path, element) => { }
// path = "settings.theme", etc.
Enter fullscreen mode Exit fullscreen mode

Template binding:

<input lm-path="fieldName" />
<input lm-path="nested.property.path" />
Enter fullscreen mode Exit fullscreen mode

Works with: <input>, <textarea>, <select>, checkboxes, radio buttons.

Why It Works Well

  • Close to vanilla JS — Components are functions, templates are template literals. No transpilation needed.
  • Small footprint — Less than 8KB for the entire framework, form binding included.
  • No magic — You can read the code and understand what's happening.

Related Tools

LemonadeJS is part of a suite of lightweight JS tools:

  • LemonadeJS - Reactive micro-framework
  • Jspreadsheet - Excel-like spreadsheets
  • CalendarJS - A complete collection of calendar and schedule components
  • Jsuites - UI components (calendar, dropdown, color picker)

Top comments (0)