Yet another micro package for reactive UI based on event-driven programming

ndrean

NDREAN

Posted on June 29, 2023

Yet another micro package for reactive UI based on event-driven programming

The ideas of this front-end micro package are:

  • use event-driven programming: you write to the state and this triggers events with callbacks.

  • write HTML as strings: no templating, just normal Javascript interpolation,

  • use datasets to references listeners on elements,

  • declare state variables. They have properties such as:

    • .val which is a setter and a getter,
    • .target which sets the DOM element which will receive the event triggered by a state change,
    • .resp which sets desired rendering.
  • diff on the data, not the DOM.

It gives a simple reactive pattern based on emitting events and limits the rendering of arrays with a diffing function on the data.

Take the example of a button to increment a counter. You have declared a state object "counter". The state is managed by the package.

<button data-click="incr" type="button">Incr counter</button>
Enter fullscreen mode Exit fullscreen mode

You have set up an "onclick" listener on this element via the dataset data-click. When clicked, it will run the function you declared:

incr: ()=> counter += 1
Enter fullscreen mode Exit fullscreen mode

This mutates the state. Another DOM element, say a <p id="count"> should display the updated value. You target this element for this state variable, and set counter.target= #count. The state is managed by the package and will emit a change Event that targets this "#count" element.

If you set on this element an "onchange" listener - with a dataset data-change="displayCounter" - like:

<p id="count" data-change="displayCounter"></p>
Enter fullscreen mode Exit fullscreen mode

then the "onchange" listener of this element will react to the event and run the "displayCounter" function, which should generally return an HTML string element.

To finally render, we assign the output to the key counter.resp of the state. This will mutate again the "counter" state:

displayCounter: ()=> counter.resp = `<span>${counter}</span>`
Enter fullscreen mode Exit fullscreen mode

The package will take this state mutation into account and continue to a rendering function that eventually does something like:

count.innerHTML = counter.resp
Enter fullscreen mode Exit fullscreen mode
  • when the state object is an array, calculate the diff on the data, between the new data and the old, and render only the differences (limited to the whole row): a "delete", a "new", an "append", an "update".

The micro package named "biny" implements these ideas. It is very close to Vanilla JS but modest in the sense that it is short (1.4kB, 250LOC) and cannot deal with computed or derived state. This can be easily managed by another state variable. It performs almost like vanilla JS when you run the JS-framework bench test. The code to write the famous todoMVC is also quite compact.

Source: https://github.com/ndrean/binyJS

A simple counter looks like:

import B from "binyjs";

const counter = B.state({ val: 0 }),
  actions = B.Actions({
    inc: () => (counter.val += 1),
    display: () => {
      counter.target = count;
      counter.resp = `<span>binyJS state: ${counter.val}</span>`;
    },
  });

window.onload = () => actions.display();

app.innerHTML = `
  <div>
    <h1>Hello biny</h1>
    <div>
      <button type="button" data-click="inc">
      <span data-change="display" id="count"></span>
      </button>
    </div> 
  </div>
`;
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
ndrean
NDREAN

Posted on June 29, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related