WebReflection / hyperHTML

A Fast & Light Virtual DOM Alternative
ISC License
3.06k stars 112 forks source link

How to use with a store. #374

Closed iogrt closed 5 years ago

iogrt commented 5 years ago

First of all, thank you for building this library, I believe it's the start of a revolution in frontend development when people realise the disadvantages of working with such a big stack and fragile technologies.

I'm a big fan of "React-style" function components, and I love how simple it is in hyperHtml without any use of JSX.

The last obstacle I am facing before starting a hyperHTML project is how would I use a store. If I do something like this:

const createButton = (props) => wire(props)`<button onclick="${props.onclick}">${props.text}</button>`

const buttonProps = {
  text: "aa",
  onclick: render,
};

function render() {
  bind(document.body)`<div>
    Lorem Ipsum
    ${createButton(buttonProps)}
  </div>`
}
render();

The button won't reload on rerender, which is awesome, but if this component uses some kind of state:

const createButton = (props) => wire(props)`<button onclick="${props.onclick}">${props.text}</button>`

const buttonProps = {
  text: (state) => state.text,
  onclick: render,
};

function render() {
  bind(document.body)`<div>
    Lorem ipsum
    ${createButton(buttonProps)}
  </div>`
}
render();

The component won't rerender with state, and if I create a new object in the render function with the state, the component will always rerender.

I want to only rerender if the state used changes, is something like this possible? I haven't found a solution yet.

I don't worry much if the performance is a bit worse, this is just a fun side project for myself.

pinguxx commented 5 years ago

you can take a look here: https://dev.to/pinguxx/easy-apps-with-hyperhtml-3-1m3l im usng state using component, but i believe it can be done without the component part and use re-rendering using the function that bind returns

WebReflection commented 5 years ago

beside following the excellent @pinguxx tutorial, you could also use a simple utility that automatically update the component when its related properties change.

const AutoUpdate = Component => function (props) {
  if (!AutoUpdate.$.has(props)) {
    AutoUpdate.$.add(props);
    Object.keys(props).forEach(key => {
      const desc = Object.getOwnPropertyDescriptor(props, key);
      if (desc.configurable) {
        if ('value' in desc) {
          let {value} = desc;
          delete desc.value;
          delete desc.writable;
          desc.get = () => value;
          desc.set = $ => {
            value = $;
            Component.apply(this, arguments);
          };
          Object.defineProperty(props, key, desc);
        } else if ('set' in desc) {
          let {set} = desc;
          desc.set = $ => {
            set.call(props, $);
            Component.apply(this, arguments);
          };
          Object.defineProperty(props, key, desc);
        }
      }
    });
  }
  return Component.apply(this, arguments);
};

AutoUpdate.$ = new WeakSet;

With above utility, you can setup your component as such:

const Button = AutoUpdate(props => wire(props)`
  <button onclick="${props.onclick}">
    ${props.text}
  </button>`);

and then render it through any object with properties that will automatically update the view when any of the properties changes:

function render() {
  bind(document.body)`<div>
    Lorem Ipsum
    ${Button(props)}
  </div>`
}

render();

You can see a live working demo of above example.

I hope these two answers helped 👋

WebReflection commented 5 years ago

@Duarte-Dias I've also created a simple components based projects where you just pass any model/store/props to any component and everything magically happens without you needing to taking care of anything.

It's called hypersimple, and the most basic example is based on yours:

import {comp, html, render} from 'hypersimple';

// components
const Button = comp(model => html`
  <button onclick=${model.onclick}>
    ${model.text}
  </button>
`);

// main App: just like any component
const App = comp(model => html`
  Lorem ipsum: ${model.count}
  <br>
  ${Button(model.button)}
`);

// model: it will be mutated to trigger updates on changes
const model = {
  count: 0,
  button: {
    text: 'increment',
    onclick() {
      // will update the view right away
      model.count += 1;
    }
  }
};

// render
render(document.body, () => App(model));

You can see it live in this Code Pen.

iogrt commented 5 years ago

Wow, your solution is brilliant! I bumped my head many times trying to implement this AutoUpdate but I lack these deep javascript skills.

I actually started looking for other tools in the meantime and found Choo.js, but that solution still uses dom-diffing and is a little bit more "frameworky" than I'd like.

I will definitely go back to hyperHTML / hypersimple and try this. Once again, congratulations on your amazing work!