preactjs / preact-custom-element

Wrap your component up as a custom element
MIT License
360 stars 52 forks source link

Web component interop improvements #40

Closed ffriedl89 closed 4 years ago

ffriedl89 commented 4 years ago

Hey, I've tinkered around quite a bit with using Preact and the preact-custom-element package to write Preact and "compile" to custom elements. This would be insanely helpful to provide a non preact version of the components for users that can't or don't want to use Preact for building their applications.

One major hurdle that I am facing is that parent - child communication is needed in composition usecases. E.g.

<x-tab-group>
  <x-tab>First</x-tab>
  <x-tab>Second</x-tab>
</x-tab-group>

The user should not have to worry about hooking up the tabs with the tabgroup correctly, but the tabgroup should take care of this.

In a purely preact world I would do this by using cloneElement and extending the props of the children to pass down a onActive prop that gets called whenever a tab is clicked. The tab-group can then react to any tab being clicked, when both of those elements are registered as custom elements.

function TabGroup({ children }) {
    const extendedChildren = toChildArray(children).map(child => {
        if (child && child.type) {
            return cloneElement(child, {
                onActive: ...
            });
        }
        return child;
    });

    return <div class="children">{extendedChildren}</div>;
}

The props like the onActive prop are not passed down to the children. Therefore parent-child usecases cannot be realised right now if both components are custom elements.

I've created a rough test case in a fork to outline the current state. https://github.com/ffriedl89/preact-custom-element/blob/wc-interop-props-fns/src/index.test.js#L106

The way to compile to custom-elements is already so promising that I really wanna explore this further. I am new to the preact world and probably not of much help for coming up with a solution, but if I can help in any way just let me know.

Any help on this is highly appreciated!  🚀

marvinhagemeister commented 4 years ago

Whilst cloneElement does the job pretty well, using context may be preferable. It avoids the case with intermediate children potentially breaking the UI whenever a Fragment is placed in-between somewhere. This is an issue I encounter frequently in (P)React applications, even without any web components at play.

For supporting the cloneElement support we need a different approach as we currently patch in a <slot>-Element in-between. The props are probably placed on that node instead of the actual child. Need to think about it more 👍

ffriedl89 commented 4 years ago

@marvinhagemeister Thanks for the fast response and input 👍 I've tested the context approach as well and added a test case as part of https://github.com/ffriedl89/preact-custom-element/blob/custom-element-context-tests/src/index.test.js#L124 It seems that the initial context value is taken, but values set via the Provider are not passed down correctly to children that are custom elements as well.

I think the context approach would also work nicely to handle this communication.

marvinhagemeister commented 4 years ago

@ffriedl89 I think I found a solution to this. Was may more challenging than anticipated! Learned that we can use native Events to forward Preact's context value, see #43 .

Thank you so much for the test case! It helped a lot in narrowing down potential solutions to a working one :+1: