UMAI, a refreshing inspired by MithrilJS and HyperApp, library
Tags: umai
Published: 2024-06-22 23:44:40

One little thing I have always found annoying in Mithril is returning **view** methods in components.

[UmaiJS](https://github.com/kevinfiol/umai) is heavily inspired by MithrilJS, no views, no hooks, no signals.
Components can easily be refrenced.

For side projects, it is very nice.

```js
import { m, mount } from 'umai';

let count = 0;

const Counter = () => (
  <div>
    <div>Counter : {count}</div>
    <button  onclick={() => count++}>INC</button>
  </div>
);

mount(document.body, Counter);
```

You can play with it here : [Demo](https://flems.io/#0=N4IgtglgJlA2CmIBcB2AbAOgCwCYA0IAZhAgM7IDaoAdgIZiJIgYAWALmLCAQMYD21NvEHIQAYT4BXQfABO3EKXgIebCAPJMADKhABfPDXqNmAK3K8BQkUwD0AKnsACAALmAHk7BP7tgDrUEGAADnyybE7AXnheUoJOek6EsnzeAOSSYLQQaQDcAQEIEfzSEQC8Tlr51AH81KQREqVyThUAFACUrQB8Tm0BTk4APFAQAG7dA4PDoxNNMrJOSJElgnpDtrOT1NPDAEaSbGwCgwI8sBA8ANZlwJ09TqtsANTPet0AkgByYhsHRwJtoMNlsAh1qgEwHE2G0oHweJlhGwMHs+FAAJ4xeZCWTghT8EIkOSiPa0PbKBRKFRqDSiNC6AxGBiiDA8UgWEB1axsElo9HALKyADmEGoSCwWmCnlohz4uSy7gAtAB3aBsFhINAAVkl7lyF2o8EVLHgECF7CQAEYMGhcoQrIrSBAAF7wK0ADiluX4sDCSAAxFgg7lgrQYKKhUgtE5Lbq9KiMXh-sdqHhRcFDsB7YJFYR6CR0UgodQ+KRQzx4HoCtRWJEpj6-U5ZPAoAE9JTlPBVOp6qIAMxWnD6AC6eiAA)

Here is demo of statefull components:

```js

const Counter = () => {
  let count = 0;
  return () => 
      <div>
        <div>Counter : {count}</div>
        <button  onclick={() => count++}>
          INC
        </button>
      </div>
};

const App  = () => (
  <div>
    {[...Array(5)].map(() => <Counter />)}
  </div>

)
mount(document.body, App);
```

[Demo](https://flems.io/#0=N4IgtglgJlA2CmIBcB2AbAOgCwCYA0IAZhAgM7IDaoAdgIZiJIgYAWALmLCAQMYD21NvEHIQAYT4BXQfABO3EKXgIebCAPJMADKhABfPDXqNmAK3K8BQkUwD0AKnsACAALmAHk7BP7tgDrUEGAADnyybE7AXnheUoJOek6EsnzeAOSSYLQQaQDcAQH81KQREtJCsk4AvE4AFACU1QB8kQFOTggR-OXVTlr51O2y8GySsoMNzU5t7bMAPFAQAG5NM7PziytlMpVIkd2CenO2m6uD6-MARpJsbALtAjywEDwA1lXAk1UtB2wA1H89GcLhcAJIAOTEawux2utwEwJhJ2WZz0A0KGgiAEFgsF2jUvi1ajMFijoXNthUnLYWtNzu0KXEqTT2uTKXJqbS2UyOSy6QzkSsCtR6gEwEzalA+DxMsI2BhLnwoABPGI44L1XIKfghEhyUSXWiXZQKJQqNQaUQ4ADMSBwaAAtDotPpDCA6AxRBgeKQLCAitY2AalcrgFlZABzCDUJBYLTBTy0G58XJZdwOgDu0DYLCQaAArPH3LlntR4A6WPAIBH2EgAIwYNC5QhWB2kCAAL3g9YAHAncvxYGEkABiLDj3LBWgwaMRpBaJx1ot6RUqvBwu7UPDR4I3YAtwQOwj0EjKpDi6h8UhTnjwPTC1itc6D4dOYZQAJ6U3KeCqdTFURbTrHB9AAXT0IA)

Reference elements in UMAI :

```js
/** @jsx m */

import { m, mount, redraw } from 'umai';

let count = 0;
const MAX = 5 

const qs = (elt) => (selector) => elt.querySelector(selector);
const bindClickEvt = (elt, handler) => elt.addEventListener("click", handler);
const incBy = (value) => () => count += value;
  
const ref_counter = (counter_elt) => {
  
  const $ = qs(counter_elt);
  
  const [binc, bdec, title] = [".inc", ".dec", ".title"].map($);
  
  let setTitleColor = (color) => title.style.color = color;
  
  bindClickEvt(binc, incBy(1));
  bindClickEvt(bdec,  incBy(-1));
  bindClickEvt(counter_elt, () => {
    count > MAX ? setTitleColor("blue") : setTitleColor("red");
    redraw();
  })
};

const Counter = () => (
  <div dom={ref_counter}>
    <div class="title">Counter : {count}</div>
    <button class="inc">INC</button>
    <button class="dec">DEC</button>
  </div>
);

mount(document.body, Counter);

```
[demo](https://flems.io/#0=N4IgtglgJlA2CmIBcB2AbAOgCwCYA0IAZhAgM7IDaoAdgIZiJIgYAWALmLCAQMYD21NvEHIQAYT4BXQfABO3EKXgIebCAPJMADKhABfPDXqNmAK3K8BQkUwD0AKnsACAALmAHk7BP7tgDrUARBgAA58smxOwF54XlKCsbLwULK0AO5Oek6EsnzeAOSSYLQQ+QDcAQEIkfzSkQC8TloV1PzUpJEAsgCCABpOjQCsTpWtGpEAjqQDTgAUymwAlAMAfHNKKmzhy-VrCxgTknIAngDKyvCq4bMbl1uyiy1tHU4ARhDUUGKwEDwA1gBRABuDTmC1iLFonwQD1WTn2tBgwOEbAAMhAOsI5LM-CAeD9-riIVC4HJHgFnpEPjwAELHGazIG0WBHHZrWZspy1QROADUjSZLPgLScIzG7UiSUIAH1uUJZAy5XJpQtOcAAqKNVzxk4ACQzKazJWyFWwJYisWiylOCjvVqxV5QS6xNRsBAAXRmFFxGGpRJGzCdPH9PtdCFx7owxRCs115OomoTTmqTiUbAAKhA3fAJLBwoq+HnYbsnGH4BgOscEBh+EWZrXwhatXavgTASDZnaeLFqXTZgBGRbx0Ut76-dtsTtB2JOXvHWYAWkHw7eH1b4+Bk+NprYsQ5cPVSat8Uiax6-QA-Kn4Bmswhc9dca8hbjlkhr7fsw-ZDiQEkoK+Fqiv+qRpByFp6IsAR6C0FI6hIdRyAynI4kmAA8UAQECThQHk9TAFKsonnIegrFqooYVhXKwLQpCkPUuJlriKwITICrvsAcp6GhtiYUCZFHk4aGvJIbBbAm+K0fRuJ+iAKwAJIAHJiDxIliQIAmihRanidRUkMSAQbMQAIgCKm2DpGlajxfECfGARgCesy4TwRQohgrx8FAxyxKx8r2YE1AKPwoQkHIoivLQrzKAotyqOo7SiFgSD9lg+iGCAdAMKINZ0cFVgohFXnHMAxSyAA5h8SBYFoISeLQol8GUxTuAuaTQGwLBIGggy1e4ZQ-NQ8ALiw8AQOV7ApRgaBlIQVgLqQEAAF7wClAAcdVlA2shIAAxFgB1lCEiKYdQ5VIFoTj9n1eied5eCWdQeAfCEonAHNggLoQ9AkMcSCOdQfCkMdPDwHooyxRc8UaKIOAXfo7p6EAA)

Simulate fetching data :

articles.js
```js
/** @jsx m */

import { m, mount, redraw } from "umai";

function sleep(ms) {
  return new Promise((res) => {
    setTimeout(res, ms);
  });
}

async function getArticles() {
  await sleep(State.delay);
  return  ([
    { id: 1, title: "Titre1" },
    { id: 2, title: "Titre2" },
    { id: 3, title: "Titre3" },
  ]);
}

let State = {
  articles: [],
  delay: 2000,
};

function saveArticles (articles) {
  State.articles = articles;
}

function clearArticles () {
  State.articles = [];
  redraw ();
}

async function fetchHN(node) {
  let loader = node.querySelector(".loader");
  loader.style.display = "block";
  clearArticles ();
  try {
    let articles = await getArticles();
    saveArticles(articles);
  } catch (error) {
    console.error("Failed to fetch articles:", error);
  } finally {
    loader.style.display = "none";
    redraw();
  }
}

const Articles = () => {
  return (
    <ul>
      {State.articles.map((a) => (
        <li>{a.title}</li>
      ))}
    </ul>    
  )
}

const Timer = () => {
  return (
    <div>
      <span>delay fetch for : </span>
      <input
        type="number"
        value={State.delay}
        oninput={(e) => {
          State.delay = +e.target.value;
        }}
      />  
      <span> ms</span>
    </div>
  )
}

const Loader = () => {
  return (
    <div class="loader" style="display:none">
      Loading...
    </div>    
  )
}

function setup(node) {
  let btnLoader = node.querySelector("button");
  btnLoader.style.display = 'block';
  btnLoader.addEventListener("click", () => fetchHN(node));
}

const App = () => (
  <div dom={setup}>
    <Timer />
    <button>Load Articles</button>
    <Loader />
    <Articles />
  </div>
);

export { App };

```

main.js
```js
import { mount } from 'umai';
import { App } from './articles.js'

mount(document.body, App)
```
[DEMO](https://flems.io/#0=N4IgtglgJlA2CmIBcA2AzAOgAwE4A0IAzvAgMYAu8UyIAhgE7kSkKEYBWhIBAZhK8gDaoAHa0wiJCAwALcmFjcQpAPYjK6miAC+eUeMlFytShy4FV6+JqmXC5AAQBlY5QcBeB8AA6Ihw9UAV3UHJAcsX21fX3gADwAHFUYvZ1d4B20lYjImNS4pABYkAEYUHT0QMQkaBiYWeDZOJUsNchoAegAqTocAAU5YhzAHTvbokQgwROTgIbwhlWDyefoqeloAdwyHHnoVYe8QQLBaCEOAbnGeYIoINQdCBHh4gAowQgBKL19-VfJA+h+ETwLYABT2kGILxeq0+HgAfN8-P4HvByAAVSbwRbkGENebvD6XZHaImRca0QgATxEpB2N1yfgA5miAIKMZisF5fHzIzanRyPeDPF4uEzwDBQEi0Klk5F-AHIl6CH4o2bQMLFeZMcgIMKHTHkVbFQ4ZPCq-zqqBhABM2oguvg+pAhtWNtNugtKQ1DjQ9sdztd8DQHvNyIAunKoiJfAhHGK3J5ef5apyGmFBOGw-4pbAZbasIWw9pib5rrTGQ9aAA3eDsuqsBwvVP1OHJ1LijAtxuebsNYnRssMu5+eoMetpwhNnmqhMSvtTzyZ4m-NabacDinU2n0isjnZo0gyAASADkXiIVFKZ8i4w5YCpaFL6B4HJepRgAI6BeD0KlOEh4AoJIXkODAHyfX9DjlfwIOfDB7CpBBJQgQh4jzKlX0OAAjB9SAAawuVUx3oCdWw3VUjUw9tYLRBwF1ffkHQcFlyDIrkYJRQgazrDlW2bPjWE47QAhMI8m1-PZ6BvFEAjyFRkMkkDDgAMVOBAoAccgVAPchxIXJBDnmJTpJXbY+DEWBYGor04N-BDyCQiUoFQ9CZSwyo1HgIjkVXKB1g2bkzOjQdaTyRx2IaV9uQRJFV3+QEmy9AAeQJYHhL1LTnLtBIaDATleZsvncRFQN82SHGS2AIHhYBaAwHUEG0ZL2mqjLyv8D4PmjWSWrSxEUVVD5yRjMKRHsBxMQkF9PBikq4ocBVErK3qXOrdqKsqtDaBEeFc3cnhDxkHYklCSr2m23bMsqiARHiQJyGu-xHPieB3EOERjmwqCQCehxq1oWAf3cYBsv2qkes2hw1Fu+7yBBl54GKxEaKhsHpUwzwAGoJWMehWIwAGgfgMzNu0SHZPaAbruSy7EXeFq6ZS9o1o2hxhpEUK7EcAAZR9n2i5GFqWvwVpRZK1oCPNCEId6QDs+hTUQhA5ZctCMKQS9gUONnZL5p9bqZDBjeZ1nZKGkahz3e5iH+V53yRha72w8gRH1gXPAdr8fz-ACcmUkBsIe7SYxATiXbd-n7OV5zXIw18AHJcJUAiE7MiP3fsp8oAAUVrdQedQjRf1A5RqoIozp1iw69JPc8Ha6zdRu5hxWXieJBdisWJYgasHCgfYQdtwJ4m0XXKqm38HCplKg-IEP4Xd1vcsIFq54XlLM5fGfyuSyKpx3-wWtZ3w5RiBIkkcWY247ktfGafZ4n4X8aGw2hvsUAhsiAxl8hAG0AAcSAbQAHZyj6GqFIMwD8rA2BAJMaYV8FhLHMhCBwCdjinDTr4BBl8Ug31QfsdBGB2gLjMAncYYBFjqBeAPUgxxrDkAwNhK8VJ5g3w+FkQCtw8g0DQMAgArOAyoBgaAYFIDLGBrQtDaCzCIyBIAACqABZVkABJAAtCpI6OggA)