Trying out Flow components in Hilla views in Vaadin 24.4 beta1

We recently released Vaadin 24.4.0.beta1. One of the new features that we’re introducing is a way of embedding Flow components in Hilla views.

Here’s how you can try that out right now. Start by downloading https://start.vaadin.com/dl?preset=react&preset=partial-prerelease, extracting it, importing it to an IDE, and running the main method in Application.java.

The basic mechanism for including Flow components in Hilla views is not new: use WebComponentExporter to create a web component that you can import to embed the component. In this case, you can reuse the Flow component that you already created in the previous step. You first need to create a simple Flow component and then a separate class that exports it as a web component.

src/main/java/com/example/application/FlowComponent.java

package com.example.application;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.notification.Notification;

public class FlowComponent extends Button {
    public FlowComponent() {
        setText("Flow component");
        addClickListener(click -> Notification.show("Hello from Flow"));
    }
}

src/main/java/com/example/application/FlowExporter.java

package com.example.application;

import com.vaadin.flow.component.WebComponentExporter;
import com.vaadin.flow.component.webcomponent.WebComponent;

public class FlowExporter extends WebComponentExporter<FlowComponent> {
    public FlowExporter() {
        super("embed-flow");
    }

    @Override
    protected void configureInstance(WebComponent<FlowComponent> webComponent, FlowComponent component) {
        // Nothing to configure in this case
    }
}

What’s new is a shorthand function for loading an exported web component from the same Vaadin server and getting that as something that can be directly rendered from JSX. You can use this to update the Hello World view to use the Flow new component.

src/main/frontend/views/@index.tsx

import { createWebComponent } from "Frontend/generated/flow/Flow";

export default function HelloWorldView() {
  return (
    <>
      This is a Hilla view <br />
      { createWebComponent("embed-flow") }
    </>
  );
}

Thanks for sharing this.

Would in this case the component be client side only, or would it be a full Flow components with server side state?

It’s a full-featured Flow component with regular server-side state and all the pros and cons that you get from that state.

Sounds good for reusing the existing Flow directory of components within Hilla. Thanks for the quick reply!

Is createWebComponent("embed-flow") memoized so it doesn’t create a new web component on each re-render?

No need to memoize anything - createWebComponent returns an element for React’s virtual DOM that is thus preserved just like any other DOM element.

The way it works can easily be observed by changing the Flow component to use e.g. a TextField and then adding some logic to the React view that triggers an asynchronous re-render. In that way, you can notice that the cursor position and text selection in the text field remains over re-renders.

One simple way of doing an async re-render is with a button that updates a useState variable through setTimeout:

import { Button } from "@vaadin/react-components";
import { createWebComponent } from "Frontend/generated/flow/Flow";
import { useState } from "react";

export default function Unified() {
    const [counter, setCounter] = useState(0);

    return <>
        <Button onClick={() => setTimeout(() => setCounter(x => x + 1), 2000)}>{counter}</Button>
        { createWebComponent("embed-flow") }
    </>
}
1 Like

Ok, good. It’s just that the thought of calling a createSomething method in the template looks like a performance red flag.

The naming is based on React.createElement which has similar performance characteristics. But I do also see that this association isn’t 100% clear. We should reconsider the name. Might even consider turning it into a proper JSX element, i.e. <FlowComponent name="my-component" />.

2 Likes

Having it as a proper JSX element sounds like a good idea. Makes it more fluent to the React style.

1 Like

We can even generate the component in frontend/generated to make it more discoverable. So, it could look like:

import { Button } from "@vaadin/react-components";
import EmbedFlow from "Frontend/generated/flow/components/EmbedFlow.js";
import { useState } from "react";

export default function Unified() {
    const [counter, setCounter] = useState(0);

    return <>
        <Button onClick={() => setTimeout(() => setCounter(x => x + 1), 2000)}>{counter}</Button>
        <EmbedFlow />
    </>
}

Should just note that generation becomes more complicated when also taking attributes into account. The documentation is already suggesting that you create a JSX wrapper in the application.