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

Creation of exclusive hooks for web-components #10

Closed UpperCod closed 3 years ago

UpperCod commented 5 years ago

Atomico currently has a React-based hook approach, basic support covers, useState, useEffect, useMemo, useRef and useReducer.

Now I'm looking to introduce exclusive hooks to work with web-components.

useHost

returns a ref, whose current is a getter that points to the current DOM container, the goal is simple to remove the host, example the same web-component, whether to issue events or create other hooks

Actual state

import { h, customElement, useRef } from "atomico";

function WC() {
    let ref = useRef();
    useEffect(() => {
        console.log({
            state: "mounted",
            target: ref.current
        });
    }, []);
    return <host ref={ref} />;
}

customElement("w-c", WC);

the problem with this is that network, must be transmitted between hooks, to create effects., use Host avoids the need to transmit the reference

import { h, customElement, useHost } from "atomico";

function useLogMounted() {
    let ref = useHost();
    useEffect(() => {
        console.log({
            state: "mounted",
            target: ref.current
        });
    }, []);
}

function WC() {
    useLogMounted();
    return <host />;
}

customElement("w-c", WC);

improve shared logic.

Other hooks in development

useProps

thanks to useHost, you can easily interact with a property of web-component observables.

let [ value, setValue] = useObservable("value");

useEvent

allows to create an event to be dispatched by the web-component.

let emitMyEvent = useEvent("my-event",{bubble:true});

useChannel

allows to create a unique context based on customEvent, useful for communication between web-components.

let [message, setMessage] = useChannel(GlobalSymbol);

This issuu is open to add logic in the form of hooks that enhance the creation and reuse of logic between web-components

threeid commented 5 years ago

I have some ideas like useForm, useStyle, useKeyframes, useSpring, useScript. Do you have any guide on how to implements custom hook?

UpperCod commented 5 years ago

@threeid I usually use style using the shadow Dom, although atomico support also supports CSSStyleSheet,eg :

/** option 1, tag style **/
function WC1() {
    return (
        <host shadowDom>
            <style>{`:host{display:block;background:red}`}</style>
            <h1>any</h1>
        </host>
    );
}
/** option 2, CSSStyleSheet **/
function WC2() {
    return (
        <host shadowDom>
            <h1>any</h1>
        </host>
    );
}

WC2.styles = [
    css`
        :host {
            display: block;
            background: red;
        }
    `
];

This has prevented me from implementing useStyle oruseKeyframes, but it would be nice to know more about what these hooks should do in order to develop it together.

I'm curious about the logic that useForm and useScript should have. about useSpring, the render tolerates independent css handling, so we could create this hook based on a library like animejs, popmotion or Element.animate.

useObservable, will soon be updated

Normally use Observer requires the static statement observables, eg:

function MyWebComponent() {
    let [count = 0, setCount] = useObserver("count");
    return (
        <host>
            {count}
            <button onclick={() => setCount(count + 1)}>increment</button>
        </host>
    );
}

MyWebComponent.observables = {
    count: Number
};

this forces the web-component to depend on an external structure that is not very reusable. with the update you will no longer need to declare the 'observables` as static property,eg:

function MyWebComponent() {
    let [count = 0, setCount] = useObserver("count");
    return (
        <host>
            {count}
            <button onclick={() => setCount(count + 1)}>increment</button>
        </host>
    );
}
// Unnecessary code
// MyWebComponent.observables = {
//  count: Number
// };

the benefit of this is the ability to create custom hooks that define properties in the web component, without the need for a superior declaration... and it's also friendly with tree shaking

threeid commented 5 years ago

For useForm, https://react-hook-form.com/ is great reference. For useSpring, https://github.com/framer/motion, https://github.com/react-spring/react-spring are great references.

And for the others that i mentioned, i was mistaken. Let's forget about that. 😅

I prefer replacing MyWebComponent.observables with

MyWebComponent.props = {
  count: Number
};

That will help debugging, code reading and IDE displays type information correctly.

For useObserver, i would like to see something like https://github.com/mobxjs/mobx-react#uselocalstore-hook.

let counter = useObserver({
  value: 0,
  inc() { this.value += 1 },
  dec() { this.value += 1 }
});

...
<host>
  {count}
  <button onclick={counter.inc}>increment</button>
  <button onclick={counter.dec}>decrement</button>
</host>

With your ideas, useObserver look nearly like useState but without MyWebComponent.observables.

UpperCod commented 5 years ago

@threeid I think it's an excellent idea to use props vs observables, it's easier to understand, the following version(0.9.0) have the following changes:

  1. WebComponent.observables => WebComponent.props
  2. useObservable => useProp
  3. new way of expressing the scheme for props
function WebComponent() {
    let [value, setValue] = useProp("value");
    return (
        <host>
            <h1>my value {value}</h1>
            <input oninput={({ target }) => setValue(target.value)} />
        </host>
    );
}

WebComponent.props = {
    value: Number,
    checked: {
        type: Boolean,
        reflect: true, // reflects property as attribute
        value: false // initial value
    }
};

I'm also considering for this release, remove the camelcase event support, eg onClick, to be more friendly with custom events, eg onMyCustomEvent

useProp unlike useState, allows the prop to be transparent in the webcomponent, eg.

let wc = document.querySelector("web-component");
console.log(wc.value);

I hope these changes are to your liking

dy commented 4 years ago

My two cents if you don't mind.

  1. Observable is a proposed standard, well-implemented by RxJS and others. Calling attributes an observable causes confusion. There's already useObservable hook, connecting rxjs/observables to component - quite useful by the way! I'd suggest calling it useAttribute or useAttr.
  2. I'd suggest instead of creating a collection of hooks, duplicating react hooks, allow direct react/preact/etc hooks. The technique is implemented in enhook, that attaches any framework hooks to regular function.
  3. useChannel is actually useStore hook.
UpperCod commented 4 years ago

@dy thank you for your comment!

  1. Your idea is great, but atomico it needs that the implementation of hooks be part of the core, since it looks for certain special behaviors with hooks like useProp, useEvent and others in the future.

  2. The objective of useChannel is similar to that of useStore, but I have removed this hook, in favor of the DOM's native event system for synchronization between web-components, example.

The previous example shows how the event manages to synchronize the parent and child.

always attentive to new ideas