Docs

Documentation versions (currently viewingVaadin 24)

Using React Components in Flow

Using React components in Flow.

React is a popular JS-based frontend UI library with a wide adoption and many components. This documentation page explains how to integrate existing React components with Flow by using an intermediate adapter Web Component, and then using it in Java applications.

This integration involves two parts: creating a Java class for the server-side adapter component; and a client-side adapter counterpart with TypeScript and React. For examples here, the React Colorful color picker component is used.

Server-Side Adapter Java Component

For the server-side part of the integration, create a Java class that extends from ReactAdapterComponent like so:

@NpmPackage(value = "react-colorful", version = "5.6.1") // (1)
@JsModule("./demo/flow/integration/react/rgba-color-picker.tsx") // (2)
@Tag("rgba-color-picker") // (3)
public class RgbaColorPicker extends ReactAdapterComponent {


}
  1. Since this will integrate a third-party React component, the @NpmPackage annotation is added to get it installed from npm.

  2. The @Tag annotation defines the DOM element name of the adapter Web Component (see below).

  3. The client-side part of the integration (i.e., the adapter Web Component) is yet to be defined and implemented in TypeScript. Therefore, you’ll need a separate file for it. Although this file will be created later, the @JsModule annotation is added here to import it.

Java and React States Synchronized

The Web Component adapter allows the state to be sent from Java to React and back by using named properties and events in the intermediate Web Component. On the server side, the ReactAdapterComponent class provides the following APIs for synchronizing the state with the adapter Web Component:

  • setState(String, T) method is for sending the state from the server;

  • getState(String, Class<T>) method is used for retrieving the current value from the client; and

  • addStateChangeListener(String, Class<T>, Consumer<T>) adds a server-side listener for state changes in the client.

You can use these methods to implement Java component property getters and setters, and event APIs. The example here uses the synchronized state for the getColor() and setColor(RgbaColor) accessor methods:

public class RgbaColorPicker extends ReactAdapterComponent {

    public RgbaColor getColor() {
        return getState("color", RgbaColor.class);
    }

    public void setColor(RgbaColor color) {
        setState("color", color);
    }

}
Note
Setting Initial Value
Call setState from the Java constructor to ensure the state is initialized from Java. Doing so enables @PreserveOnRefresh to work correctly.

Next, invoke setColor(RgbaColor) from the constructor to define the initial state:

public class RgbaColorPicker extends ReactAdapterComponent {

    public RgbaColorPicker() {
        setColor(new RgbaColor(255, 0, 0, 0.5));
    }

}

Also, add the addColorChangeListener(Consumer<T>) state change callback:

public class RgbaColorPicker extends ReactAdapterComponent {

    public void addColorChangeListener(SerializableConsumer<RgbaColor> listener) {
        addStateChangeListener("color", RgbaColor.class, listener);
    }

}

Using Non-Primitive Types

On the client, the React state can be any JS value. To support that, the methods above accept Java boxed primitive types, as well as Beans and collections that are representable with JSON.

In the example, the component has the color property, which is a JS object with the following format:

type RgbaColor = {
  r: number;
  g: number;
  b: number;
  a: number;
};

For representing such object types in Java, an easy option is to define a Bean using a record. The following example shows the RgbaColor bean, which was used in the RgbaColorPicker example above:

public record RgbaColor(int r, int g, int b, double a) {

    @Override
    public String toString() {
        return "{ r: %s, g: %s, b: %s, a: %s }".formatted(r, g, b, a);
    }

}

Client-Side Adapter Web Component

Flow provides the ReactAdapterElement TypeScript class for implementing Web Components that render with React.

The following example uses ReactAdapterElement to create the <rgba-color-picker> Web Component, which renders the <RgbaColorPicker> third-party React component in the client:

class RgbaColorPickerElement extends ReactAdapterElement {
  protected override render(hooks: RenderHooks): ReactElement | null { // (1)
    const [color, setColor] =
      hooks.useState<RgbaColor>("color"); // (2)

    return <RgbaColorPicker
      color={color}
      onChange={setColor}
    />; // (3)
  }
}

customElements.define(
  "rgba-color-picker",
  RgbaColorPickerElement
); // (4)
  1. The main part of the Web Component is the render(RenderHooks) callback, which defines the rendering using React JSX tags. You could also define state and events using the APIs provided with the hooks argument.

  2. This example calls hooks.useState<RgbaColor>("color") to define that the color property of the adapter Web Component is going to be used for synchronizing the state with the server.

  3. The JSX rendering uses the adapter state (i.e., color and setColor) with the <RgbaColorPicker> React component, following the third-party component’s API.

  4. As usual with Web Components, the JS class is registered for its DOM element name using CustomElements.define() standard web API.

RenderHooks APIs

The RenderHooks render callback argument provides the following methods:

  • useState<T>(string) method defines and uses a named state JS property of the adapter Web Component; and

  • useCustomEvent<T | undefined>(string, CustomEventInit<T>) method returns a simple to use callback that dispatches a Custom Event with the given name on the adapter Web Component:

interface PersonData {
    name: string,
    age?: number
}

class PersonDataElement extends ReactAdapterElement {
    protected override render(hooks: RenderHooks): ReactElement | null {
        const fireUpdateEvent = hooks.useCustomEvent<PersonData>("update",
            // optional event detail:
            { detail: { name: "John", age: 42 } }
        );
        return (
            <>
                <VerticalLayout>
                    <Button onClick={() => fireUpdateEvent({ name: "Mike" })}>
                        Update
                    </Button>
                </VerticalLayout>
            </>
        );
    }
}

When the user action is not related to a state change, you could use this hook and subscribe to this event in Java:

public void addPersonDataUpdateListener() {
    getElement().addEventListener("update", event -> {
        JsonObject person = event.getEventData();
        // person update code goes here...
    }).addEventData("event.detail");
}