vaadin / flow

Vaadin Flow is a Java framework binding Vaadin web components to Java. This is part of Vaadin 10+.
Apache License 2.0
592 stars 163 forks source link

Make it possible to get ReactAdapterComponent functionality with some other super class #19276

Open jcgueriaud1 opened 3 months ago

jcgueriaud1 commented 3 months ago

Describe your motivation

ReactAdapterComponent is an abstract implementation of an adapter for integrating with React components.

It extends from the Component class, as we can inherit from multiple classes in Java we can't use directly AbstractSinglePropertyField to bind a Field to a React component.

Describe the solution you'd like

If we transform the adapter to an interface with default methods it will allow to use directly AbstractSinglePropertyField.

An example of the implementation can be found here: https://github.com/jcgueriaud1/vaadin-react-field/blob/main/src/main/java/com/example/application/ui/react/prototype/IReactAdapterComponent.java

Describe alternatives you've considered

None. The idea is to keep the Java class similar if the frontend is implemented in React or Lit (or plain Javascript). The interface is purely the copy of the abstract interface, unfortunately it's required to transform the protected method to default public.

mshabarov commented 3 months ago

We can create a new interface and move there the functionality from ReactAdapterComponent, and then keep the ReactAdapterComponent abstract class as a shortcut for simple cases that implements this new interface and extends Component. What do you think?

Also, maybe a separate docs issue - we need an example of how to wrap a React component as a Flow field, e.g. to be able to use it with the Binder on a form.

jcgueriaud1 commented 3 months ago

As long as we can have an interface to avoid the boilerplate, I'm not against a abstract class in addition. I don't know if that will make things easier.

With ReactAdapterComponent class:

@JsModule("./react-component.tsx")
@Tag("react-component")
public class ReactMonthYearField
        extends ReactAdapterComponent {

With the interface:

@JsModule("./react-component.tsx")
@Tag("react-component")
public class ReactMonthYearField
        extends Component
        implements IReactAdapter {

Also, maybe a separate docs issue - we need an example of how to wrap a React component as a Flow field, e.g. to be able to use it with the Binder on a form.

Or a blog post, it depends a bit of what you want to implement (HasTooltip, HasLabel, HasHelper, HasValidation,...), that can be tough. And that could be done for Lit or a webcomponent also :).

But at least in the repository I linked (https://github.com/jcgueriaud1/vaadin-react-field/), you have an example for both React and Lit.

Legioth commented 3 months ago

I don't think an interface is the right option since interfaces define the public API of a class and you wouldn't want to make methods like setState part of the API that users of your component would see.

Instead, there might be an opportunity for a "connector" that you can use from your component. A simple example could thus look like this:

@Tag("some-tag")
@JsModule("./someModule")
public class MyComponent extends SomeOtherComponent {
  private final ReactComponentConnector connector = new ReactComponentConnector(this);
  public void setValue(String value) {
    connector.setState("value", value);
  }
}
jcgueriaud1 commented 3 months ago

I've just noticed that I'm not using the interface at all, since I'm using getElement().setProperty("invalid", invalid); and not setState("invalid",invalid);

I think the connector could handle the other case and leave everything protected. (I'm not sure if everything can be handled with properties)

Legioth commented 3 months ago

The implementation of ReactAdapterComponent is basically just some helpers around Element::setPropertyJson, Element::getPropertyRaw and Element::addPropertyChangeListener. All of the actual magic is handled by Flow and/or the client-side base class for the custom element.

knoobie commented 3 months ago

thumbs up for the connector idea This would allow the usage of AbstractSinglePropertyField and other important classes without cluttering the public interface.