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:


Compiled frontend code:

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