Design
How is Java compiled for the browser?
React4j uses J2CL and the Closure compiler through Bazel. Development uses a BUNDLE build with explicit runtime definitions; optimized production uses ADVANCED compilation. See Project Setup for Java, JSpecify and processor configuration.
Where is the equivalent of React's stateless function components?
The project originally supported stateless functional components defined by an interface that had
a single render method. You could define the rendering of the method using a simple lambda method
that accepted a props object. This resulted in code that looked like:
class MyComponent {
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
static class Props extends BaseProps {
int param;
}
@View
public static final StatelessComponent<Props> COMPONENT = (props) -> {
return span(new HtmlProps().className("myclass"), String.valueOf( props.param ));
};
}
Contrast this with code that uses normal components:
@View
class MyComponent extends Component<MyComponent.Props, BaseState> {
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
static class Props extends BaseProps {
int param;
}
public ReactElement<?, ?> render() {
return span(new HtmlProps().className("myclass"), String.valueOf( props.param ));
};
}
In java, the StatelessComponent approach seemed to offer few advantages. The two approaches were
roughly equal in the amount of ceremony required and identical from a performance standpoint. However the
StatelessComponent approach required more work if you needed to refactor the components later to add state,
or use the lifecycle callback methods.
So we removed StatelessComponent and simplified our application, the library and supporting tools. (The
annotation processor is significantly simpler as a result of this change.)
The react team has promised that at some point in the future, stateless functional components will have a better performance profile due to lower memory usage and low-level optimization opportunities within the react runtime. When this eventuates, it will be reasonably easy for React4j to take advantage of these enhancements with no changes to the way components are written. The annotation processor already analyzes the component to determine if the component uses state or if any lifecycle methods are declared. If neither of these conditions is true, React4j could transparently expose the component as a stateless functional component and take advantage of the potential performance enhancements.
Actually it turns out that functional components should be slightly faster in React 16 as there's no instance created to wrap them unlike in React 15. However this seems to be obscured by the slight overhead of React4j but when/if the performance becomes more discernible, expect React4j to begin supporting stateless functional components.
It should be noted that a month or two after we had made this decision the article "7 Reasons to Outlaw React’s Functional Components" was published and a similar sentiment arose in the Javascript community.
Why is there both NativeComponent and Component classes?
The project initially required that React4j components all extend the equivalent of the NativeComponent
and thus React4j components had to be exported as javascript objects using the @JsType(isNative = false) annotation.
This model required that the developer was very aware of the constraints of developing classes that would be
exported as javascript. This often resulted in lots of code being annotated with either @JsIgnore or
@SuppressWarnings("unusable-by-js") to avoid JsInterop compiler warnings. The developer
experience was less than optimal.
Building a parallel component hierarchy allowed us to remove the boilerplate and constraints required to export a native object to javascript. It also made it possible to add additional validation to verify that the application code interacts with the native react runtime correctly.
Why does React4j manually export types to Javascript?
React expects constructor functions and lifecycle members in a particular JavaScript shape. React4j's processor emits a native view wrapper and exports the required constructor and members explicitly. Application view classes can remain ordinary Java classes, while the generated code owns the React boundary.
Arez Integration
Which components should extend ReactArezComponent?
ReactArezComponent belongs to an earlier API. Current views use @View, and the processor generates the Arez integration for tracking views. Observable or computed state accessed during rendering schedules the view for another render when it changes. Arez integration shows the current annotated view model.
A reusable view can also accept ordinary input values from a tracking parent. The parent observes domain state and passes updated values through the generated builder, keeping the reusable view independent of that domain model.
Should components implement custom shouldComponentUpdate() method?
The processor generates update handling for the view's inputs and tracking state. Keep the annotated lifecycle and input contracts as the source of that behavior; application code does not need an inherited ReactArezComponent implementation. See Compared to React.
Why is there an 'arez' key in component state?
Generated tracking views can store debug information in React state when react4j.store_debug_data_as_state and Arez spies are enabled. The generated state includes component identity and render dependencies for inspection. This is a development aid; the optimized production defaults disable debug-state storage. The explicit mode definitions are in tools/browser/rules.bzl.
Why can't render methods update observable state?
Arez has the concept of read-write and read-only transactions and observable state should only be modified in
read-write transactions. This is enforced in development mode and assertion failures will occur if you attempt
to modify observable state in a read-only transaction. The render() method is deliberately wrapped in a read-only
transaction.
The main reason for this is to disallow a render triggering further renders and to stop potentially long render
times as other observers react to changes in observable state. The underlying react library also considers this
pattern a very bad idea conceptually and performance-wise and likewise blocks calling setState() from within
the render method.