cferdinandi / reef

A lightweight library for creating reactive, state-based components and UI.

Home Page:https://reefjs.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Listen to nested subcomponent render

Falkan3 opened this issue · comments

Hello, first of all, thanks for the awesome work.

I'm developing a plugin using reef as a reactive UI renderer, and I have a nested component structure like so:

App->Component

The component is attached to App.
I want to bind to Component's render event to create a carousel inside it after it has been rendered. So far I've only found the base reef:render which is fired every time any template has been rendered, and results in errors because the Component has not rendered any html yet.
How would I go about waiting for a specific template to finish rendering?
I tried comparing reef:render event target but to no avail (it is also called every time even if the template is not going to be rendered).

Kind regards

I managed to get the rendered component by using the reef:render target and comparing it to the class I'm looking for. Still, it has to loop over every component and perform a check to get to the desired component render. Is it possible to emit and listen to the specific component render? Or maybe wait for all renders to be completed?

@Falkan3 Apologies, as I'm a bit confused here.

The reef:render event event fires on a specific element whenever that element's content is re-rendered, and bubbles up to the window or document (just like other events like click do).

You can attach you event listener directly to the element you want to listen for renders inside.

let elem = document.querySelector('.my-carousel');
elem.addEventListener('reef:render', function (event) {
    console.log('The carousel was rendered!');
});

Historically, this was more complicated with nested components because the original element might get purged from the DOM, breaking the event listener, so I would recommend event delegation.

Today, you can now attach event listeners on specific elements using on* attributes in your template. Reef uses event delegation under-the-hood, and automatically handles when elements get added or removed from the DOM.

new Reef('#app', {
	data: {
		heading: 'Hello, world!'
	},
	template: function (props) {
		return `
			<h1>${props.heading}</h1>
			<div id="sub-component" onreef:render="onrender">
				${subcomponent.html()}
			</div>`;
	},
	listeners: {
		onrender: function (event) {
			// do something...
		}
	}
}).render();

Thank you, I was looking for the nested component delegation as the element was being purged every time the parent rerendered, as the HTML was dynamic and the nested component looked for the presence of nested elements to initialize.

Awesome. This works?

Hello, unfortunately it doesn't seem to be working. I'm using version 11.0.1. Could it be because the subcomponent HTML root is dynamic? I'm setting the html on the parent component using the data, looping through an array and appending the HTML, then waiting for the parent to render, and finally creating a Reef subcomponent for each root in the final HTML.

So using your example it would look something like this, greatly simplified:

const app = new Reef('#app', {
    data: {
        messages: [
            { text: 'Lorem ipsum' },
            { text: 'Lorem ipsum' },
        ]
    },
    template: function (props) {
        return props.messages.map((message, index) => {
            return `<div class="message" data-id="${index}" onreef:render="onRenderMessage"></div>`;
        }).join('');
    },
    listeners: {
        onRenderMessage: function (event) {
            new Reef(event.target, {
                data: {
                    text: app.data.messages[event.target.dataset.id]
                },
                template: function (props) {
                    return `<p>${props.text}</p>`;
                },
                attachTo: app
            });
        }
    }
}).render();

So, in your example there, you're creating a new Reef() instance as your event callback function.

What exactly are you trying to do?

Perhaps I'm misunderstanding, I want to create a new Reef component using elements in the parent's HTML, so that they are nested. I want to have a Reef instance for parent, and an instance for each subcomponent inside the parent. I'm certain there is a better way to do this.

For example, I have this structure of components:
App->Chat->Message
Each a Reef instance attached to its parent. To complicate things, inside message I'm creating a carousel instance using a library.

To be clear, I have accomplished what I wanted, just trying to maybe find a better way. The way I did it was to wait for the parent to render, then reinitialize all nested components:

Events.on('app.rendered', (event) => {
        if (event.target.matches('#chat') {
        ...
        // initialize new reef instances
                ChatCarousel.refs.carousels.push(new Reef(`[data-message-id="${carousel.dataset.messageId}"]`, {
                    template: (props, elem) => ChatCarousel.generateHtml(props, elem),
                    attachTo: Components.Chat.refs.chat
                }));
        ...

Yes, I'm familiar with the docs. Perhaps my issue is with the dynamic selector, in your example it is static and known beforehand. In my case each ID for the subcomponent is unique.

Ohhh I get it now! So... looking at what you're trying to do, using a Reef instance for the item isn't the approach I would use.

If the state is always tied to the parent, and you're just trying to keep your template more clean, I'd use a helper function for that instead.

function getText (item) {
    return `<p>${item.text}</p>`
}

const app = new Reef('#app', {
    data: {
        messages: [
            { text: 'Lorem ipsum' },
            { text: 'Lorem ipsum' },
        ]
    },
    template: function (props) {
        return props.messages.map((message, index) => {
            return `<div class="message" data-id="${index}">${getText(props.messages[index])}</div>`;
        }).join('');
    }
});

app.render();

It makes sense, I will try to simplify the logic, I separated the components because I wanted the code to me more modular and load on demand. Thank you for your time anyways.