atomicojs / atomico

Atomico a micro-library for creating webcomponents using only functions, hooks and virtual-dom.
https://atomicojs.dev
MIT License
1.16k stars 43 forks source link

Does not work with detached custom elements #15

Closed dy closed 4 years ago

dy commented 4 years ago

If we'd like to create and run custom element in detached tree, it is impossible to run atomico handler with it.

import { customElement, useState } from 'atomico'
function MyTag() {
  let [ count, setCount ] = useState(0)
  setTimeout(() => {
    console.log(count)
    setCount(++count)
  }, 1000)
  return {nodeType: 'host'}
}
customElement("my-tag", MyTag);
let el = document.createElement('my-tag')

This just silently ignores the component.

If we try to render the old scool react way

import { ... , render } from 'atomico'
...
render(MyTag, document.createDocumentFragment())

It gives error - hooks are not supported.

haunted doesn't have that drawback. Any plans to catch up?

dy commented 4 years ago

(I was trying to include atomico as target in enhook - I wonder if that's possible at all to create atomico component without DOM).

UpperCod commented 4 years ago

Thank you for your example, I have detected an interesting problem:

Atomico and Haunted work similarly wait for the component to be mounted, to render hooks and views, but currently differently when unmounted.

Haunted follows the setTimeout buble, Atomico pause it, see example

Atomico in version 0.13.1 correctly reintegrates the cycle(This is thanks to your observation @dy ), but it would be ideal if haunted could pause the render cycle to avoid memory loss and additional DOM work...

dy commented 4 years ago

haunted exposes State class so that it can be run independent of DOM. Is there something similar in atomico?

UpperCod commented 4 years ago

Yes, it exists undocumented and interesting. atomico/src/core/hooks.js

import { createHookCollection, useState } from "atomico";

/**
 *  createHookCollection, Create a collection of hooks, it
 * must be stored for reuse if you want to maintain the state between renders
 */
let hooks = createHookCollection(() => {
  /**
   * any render
   **/
});

/**
 * function that uses Atomic hooks
 */
function Example({ any }) {
  let [count, setCount] = useState(0);
}

/**
 * open the hooks, marking positions
 */
hooks.load(Example, { any: "param" });

/**
 * dispatch the updated life cycle
 */
hooks.updated();

/**
 * dispatch the life cycle unmount
 */
hoooks.unmount();

To implement a customRender outside of Atomico you will need to save the variable hooks of the example and then retrieve it, Atomico saves it in each instance of a web-component

In the file atomico/src/core/hooks.js, internal hooks are used as:

useRender, get the render associated with the hook useHook, It is a pattern to reducer, to know the life cycle of the hook and modify its state, eg useEffect

All this within the 3KB of Atomico

dy commented 4 years ago

Hm. I'm trying to

import { createHookCollection, useState, useEffect } from 'atomico'

function myFn() {
  let [count, setCount] = useState(0)
  useEffect(() => setCount(1), [])
  hooks.updated()
}

let hooks = createHookCollection(() => { })
hooks.load(myFn)
myFn()

And getting error:

undefined is not iterable

The useState(0) returns undefined during the second call. What am I getting wrong here?

matthewp commented 4 years ago

Hey just popping in here, I'm the maintainer of Haunted. Just wanted to say that the unmounted issue with unmounted components is an interesting one and worth discussing further. If anyone wants to open an issue in matthewp/haunted we can discuss there, I don't want to sidetrack this issue. Thanks!

UpperCod commented 4 years ago

I'm attentive to its implementation 👀, you should create a layered function to encapsulate the hook collection and retrieve it, eg

https://codepen.io/uppercod/pen/ZEEqWEg?editors=0012

import { createHookCollection,  useState } from "atomico";
/**
 * store createHookCollection
 */
let components = new Map();

/**
 * create a scope for hooks
 * @param {Function} component
 * @return {Function} render
 */
function createComponent(component) {
  let hooks = createHookCollection(render);
  /**
   * custom render that allows you to use Atomico hooks
   * @param {any} props
   */
  function render(props) {
    hooks.load(component, props);
    hooks.updated();
  }
  /**
   * save the createHookCollection
   */
  components.set(component, hooks);

  return render;
}

/**
 *  The return will be the render function to insert the component.
 */
let renderComponent = createComponent(() => {
  let [state, setState] = useState(0);
  setTimeout(() => {
    setState(state + 1);
  }, 1000);

  console.log(state);
});

renderComponent({ any: "prop" });
dy commented 4 years ago

Hmm. useEffect creates stackoverflow in your example @UpperCod:

let renderComponent = createComponent(() => {
  let [state, setState] = useState(0);
  useEffect(() => {
    setState(1);
  }, []);
});
renderComponent({})

https://codepen.io/dyv/pen/BaaqKLO?editors=1111

UpperCod commented 4 years ago

Fixed in version atomico@0.13.3, please check the link https://codepen.io/uppercod/pen/eYYPWOq?editors=0012

dy commented 4 years ago

Nice. Added enhook implementation https://github.com/unihooks/enhook/blob/master/src/provider/atomico.js