jahilldev / component-elements

Create a custom element from any component with these tiny functions (2KB GZipped, ~1KB Brotli). Preact and React currently supported
MIT License
89 stars 7 forks source link

Unable to render child elements inside my component #23

Closed Half-Shot closed 1 year ago

Half-Shot commented 1 year ago

Hi, I'm having a hell of a time trying to get the content of my custom component in the HTML side to render in the component. I've broken it down to a very small example, and it's still not working. There don't appear to be any logs in the JS side, so I want to check I'm using this properly.

import { define } from 'preactement';
import PropTypes from 'prop-types';

export default function CFPost({ title, children }) {
    return <>
                    <div className="cf-post-header">
            <h2 className="page-title-overlay">{ title }</h2>
            </div>
                <div className="cf-post-content wordpress-content charcoal-wp-content pt-3">
                <hr className="header-hr"/>
                {children}
                </div>
    </>;
}

CFPost.tagName = 'cf-post';
CFPost.propTypes = {
    title: PropTypes.string.isRequired,
}
CFPost.attributes = ['title'];

define(CFPost.tagName, () => CFPost, { attributes: CFPost.attributes });
<div class="content-main wooshIn">
  <cf-post
  title="Guests of Honour"
  datetime="2018-09-15T15:56:00+00:00"
  posttype="page">
    <p> Hello there </p>
  </cf-post>
</div>

I'd expect an output of:

<div className="cf-post-header">
    <h2 className="page-title-overlay">Guests of Honour</h2>
</div>
<div className="cf-post-content wordpress-content charcoal-wp-content pt-3">
  <hr className="header-hr"/>
  <p> Hello there </p>
</div>

but the <p> is missing entirely. Is there something obvious I need to do to enable child element rendering? I did also try with named slots with no success.

jahilldev commented 1 year ago

Hey @Half-Shot,

Thanks for reaching out. Sorry to hear your having some issues.

I can't see anything super obvious in your code snippets, at least nothing that jumps out at me as being wrong.

You can find a simple example usage of children and slots below, which is working correctly: https://github.com/jahilldev/preactement-htmlslots

Ideally, if you could provide a minimal repo illustrating the issue, I can more easily diagnose the issue and run it myself.

Sorry I can't be of more help at this time, let me know when you've got a runnable reproduction 👍

EDIT: Just for clarity, I'm guessing your using Preact rather than React, given the use of preactement?

All the best

Half-Shot commented 1 year ago

Hiya, yeah I trawled the examples for a few hours but it wasn't working out for me. I'll try to run that example locally and see if it works, and then, hopefully something will become obvious. Will give you a shout when I figure it out!

(And yep, using preact)

jahilldev commented 1 year ago

Sounds good, if you need any pointers or further help, let me know 👍

Half-Shot commented 1 year ago

Example worked locally, but copying the exact same component and HTML into my project yielded the same bug: I'm now wondering if there is something wrong with how we're using webpack that's omitting a dependency...silently and causing the DOM not to be rendered.

Half-Shot commented 1 year ago

Right, figured it out and oh my god I am both relieved and annoyed:

Having set up debug statements all over preactement and testing, I found that define.ts#onConnected was being called twice on my component. Huh, weird. As it turns out: Once with mounted = false, with NO innerHTML content, and once with mounted = true, with the expected innerHTML content. Obviously by the point of it being mounted, it wasn't interested in the inner contents so the result would be missing contents.

We have a plugin on our site called mmenu. It's a bit legacy and we're on the way to replacing it (moving to a more preact world). I noticed that in our lifecycle, the custom element would render once, then mmeenu would run a bunch of triggers on init that would call it's setPage function which seemingly moves the DOM around a lot.

Anyway, I found that by disabling mmenu, the code now works. What a frustrating yet enlightening journey. I hope this makes some sort of sense to whoever reads this, as my brain is fried :)

Half-Shot commented 1 year ago

Spoke way too soon, it's now rendering the HTML yes, but it's ignoring named slots or children and just appending it to the end of the element.

Half-Shot commented 1 year ago

The answer was to run the define after DOMContentLoaded has fired, otherwise the browser may not have loaded the HTML content yet.

jahilldev commented 1 year ago

Hey @Half-Shot, glad to hear you've got it working 👍

It sounds like the issue you were having was caused by your app code's execution order, which is always tricky to debug, especially within a legacy codebase.

Either way, great to hear it's all working.

Feel free to reach out if you run into any more issues 👍