Loading .tsx component files from addon instead of main application

Hello Support Team,

I have integrated a React component into my Jmix application and packaged it as an addon.

I would like to know how to successfully load the .tsx component file while keeping it within the addon itself, rather than having to place it in the main application.

Thank you for your assistance.

Best regards,
Thibaut

Hello!

First of all, i should notice that Vaadin’s Hilla root router is disabled for Jmix. As I understand, you already find the soultion how to integrate React into the Jmix application.

I would like to know how to successfully load the .tsx component file while keeping it within the addon itself, rather than having to place it in the main application.

Simple Answer:

Put your .tsx / .ts / .js / etc frontend files into META-INF/frontend - this is ROOT folder for all frontend resources.

Detailed answer

Detailed answer about React integration into Jmix. As you understand, Jmix (Vaadin) has own html element tree, so there is no ‘root’ element where we should begin the render.

So, firstly, we need to invent our own wheel to do this.

Simplest - make root for react - parent Dom Element where we attached our component. E.g.

file path: src/main/resources/META-INF/frontend/react-component.tsx

import React from 'react';
import { createRoot, Root } from 'react-dom/client';

class ReactComponent extends HTMLElement {
    private root?: Root;

    connectedCallback(): void {
        // single root 
        if (!this.root) {
            this.root = createRoot(this);      // otherwise this.attachShadow({mode:'open'}) - if we ran into a 
// shadow root element of vaadin (yes, it can be common HTMLElement or Shadow DOM element
        }
        this.mount();
    }

    disconnectedCallback(): void {
        this.root?.unmount();
        this.root = undefined;
    }

    private mount(): void {
        this.root!.render(<h1>this is react component</h1>);
    }
}

customElements.define('react-component', ReactComponent);
export {};

Secondary, if we building addon, we need to pack all resources into META-INF, vaadin takes resources from frontend folder, so → META-INF/frontend is our root folder

Now, add vaadin element binding:

@NpmPackage(value = "react", version = "18.2.0") // careful with version and conficts, bind version strictly due to auto-download versions by vaadin
@NpmPackage(value = "react-dom", version = "18.2.0")
@JsModule("./react-component.tsx") // as you can see, /META-INF/frontend/react-component.tsx transformed into ./react-component.tsx (root folder)
@Tag("react-component")
public class ReactComponent extends Component {


}

Now, just import this addon into your app within your module architecture, for me this is
implementation 'com.company:react-component-starter:0.0.1-SNAPSHOT'

For you this could be

api project(':react-component') - no need to rebuild every time module, gradle automatically rebuild this project if detect new changes

Tests:

For showcase, i put my component into UserListView in java view controller code (no Jmix XML Loader)

@Route(value = "users", layout = MainView.class)
@ViewController(id = "User.list")
@ViewDescriptor(path = "user-list-view.xml")
@LookupComponent("usersDataGrid")
@DialogMode(width = "64em")
public class UserListView extends StandardListView<User> {
    @ViewComponent
    private Div header;

    @Subscribe
    public void onBeforeShow(final BeforeShowEvent event) {
        header.add(new ReactComponent());
    }


}
<layout>
        <genericFilter id="genericFilter"
                       dataLoader="usersDl">
            <properties include=".*"/>
        </genericFilter>
        <div id="header"/>
        ...
</layout>

After running the app:

image

image

Compiled frontend code:

image

Conclusion

Be careful with kinda integrations, keep it in mind that each time you insert that component - each react DOM would be rendered in client’s tree.

Sources:

react-component.zip (add-on) (513.5 KB)
untitled.zip (project) (294.2 KB)

Best regard,
Dmitry

1 Like