This is quite serious! The famous JavaScript framework React.js is now available to Java as a JSweet candy.
Of course there are still details to be tuned, and the JSX syntax is not supported (yet?), but there is enough there to start having fun with React.js in Java.
I will step through some examples to give you the basics on how to program a React.js application in Java.
The easiest way to start is just to clone the project that contains some simple React examples with JSweet: https://github.com/cincheo/jsweet-examples-react. Make sure that you follow the instructions.
All the examples have a similar structure. Let’s start with the first one. Check webapp/simple-example1.html
out.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>I'm in a React app!</title> </head> <body> <div id="react-app"></div> <script src="https://cdn.jsdelivr.net/react/0.14.0-rc1/react.js"></script> <script src="https://cdn.jsdelivr.net/react/0.14.0-rc1/react-dom.js"></script> <script type="text/javascript" src="../target/js/examples/SimpleExample1.js"></script> </body> </html>
This simple default template will accept the React-rendered HTML in the react-app
div.
Note that the last included script points to the SimpleExample1.js
file. This file is automatically generated with JSweet when transpiling with mvn generate-sources
. The original file is the src/main/java/examples/SimpleExample1.java
. This is the one you have to change if you want to play around with this example.
Well, let’s now start writing React code in Java. I have taken this example from James Knelson’s blog: http://jamesknelson.com/learn-raw-react-no-jsx-flux-es6-webpack/. It just creates an HTML contact list with the React API.
package examples; import static def.react.react.Globals.createElement; import static jsweet.dom.Globals.document; import def.react.react.HTMLAttributes; import def.react.react.ReactDOM; import def.react.react.ReactElement; public class SimpleExample1 { static final HTMLAttributes EMPTY = null; public static void main(String[] args) { ReactElement> rootElement = createElement("div", EMPTY, // createElement("h1", EMPTY, "Contacts"), // createElement("ul", EMPTY, // createElement("li", EMPTY, // createElement("h2", EMPTY, "James Nelson"), // createElement("a", new HTMLAttributes() { { href = "mailto:james@jamesknelson.com"; } }, "james@jamesknelson.com")), // createElement("li", EMPTY, // createElement("h2", EMPTY, "Joe Citizen"), // createElement("a", new HTMLAttributes() { { href = "mailto:joe@example.com"; } }, "joe@example.com")))); ReactDOM.render(rootElement, document.getElementById("react-app")); } }
The entry point is the global function def.react.react.Globals.createElement
that allows you to create any HTML element with Java. In general, the first parameter of the function is the element type (e.g. li
, h2
, p
, etc.). The second one is usually an instance of def.react.react.HTMLAttributes
, which will contain the tag’s attributes values if any. Most of the time, the attributes are empty, that’s why I define an EMPTY
constant, which is of the right type (you can also pass null
, but then you have to cast to HTMLAttributes
to disambiguate the call to createElement
). Then, the third paramter is actually a vararg of elements, which are the children of the current element (inner tags if you prefer).
Once your document part is constructed in Java as a ReactElement
, all you need to do is to call ReactDOM.render
, which will insert the element into the given target element (here the react-app
div).
Since you build your HTML code in your program, you can easily control the generation with Java variables and control structures. For instance, instead of harcoding the data in your HTML, you can write a program that uses a model to generate the HTML. For example, we can define the following model in Java:
@Interface static abstract class Contact { int key; String name; @Optional String email; }
And instantiate a contact list:
Contact[] contacts = { new Contact() { { key = 1; name = "James Nelson"; email = "james@jamesknelson.com"; } }, new Contact() { { key = 2; name = "Bob"; } }, new Contact() { { key = 3; name = "Renaud Pawlak"; email = "rp@rp.com"; } }, new Contact() { { key = 4; name = "John Smith"; email = "JS@hello.com"; } } };
Then we can loop over the contacts (and even select the ones we want) to render the HTML out of the data. SimpleExample2 shows how to do it. It even filters out the contacts that have no email.
The main point about React is the ability to define more abstract components out of a model (props) and a state. In this simple contact example, a natural refactoring to do is to define a component to render the contacts. Just below is the code of that component in JSweet. Note the first type parameter, which defines the model on which the component works (here the Contact
interface).
static class ContactItem extends Component<Contact, Object> { @Override public Element render() { Contact contact = union(props); return any(createElement("li", EMPTY, // createElement("h2", EMPTY, contact.name), // createElement("a", (HTMLAttributes) $map("href", "mailto:" + contact.email), contact.email), // createElement("h3", EMPTY, contact.description)) // ); } }
Like other “candies”, the React JSweet API is automatically generated from DefinitelyTyped definition types. The React.js definitions are complex ones, which are hard to translate “as is” in Java. As a consequence, you will see that the typing is sometimes not as good as it should be. For instance, in the render()
method, I use a trick to convert a ReactElement
returned by the createElement
method to a JSX Element
(I use the any()
method, which removes typing, similarly to a cast to any
in TypeScript). Another trick I use is that I define a helper method to instantiate components: <P, S, C extends Component<P, S>> ReactElement<P> createElementFromClass(Class<C> componentClass, P props). This method is better typed than the default React one and simplifies the use of the API in that case.
The full code using the ContactItem
component is available there.
In the React example project, you will also find two simple examples taken from this React.js page: https://facebook.github.io/react/. These examples show the ability of components to react to events and self-update the UI in real time.
The first example consists of creating a timer component, that updates the UI every second. The following code shows how to define that component, which holds a state. That state is changed every second when the tick()
function is called by the timer, which automatically triggers a render()
call. Note the overriding of the componentDidMount()
and componentWillUnmount()
methods, which are part of a component’s lifecycle. It is the componentDidMount()
that creates the actual timer (setInterval(...)
is a DOM function).
@Interface static class TimerState { int secondsElapsed; } static class Timer extends Component<Object, TimerState> { String displayName = "Timer"; int interval; TimerState state = new TimerState() { { secondsElapsed = 0; } }; public void tick() { this.setState(new TimerState() { { secondsElapsed = state.secondsElapsed + 1; } }); } @Override public void componentDidMount() { interval = any(setInterval(function(this::tick), 1000)); } @Override public void componentWillUnmount() { clearInterval(interval); } @Override public Element render() { return any(createElement("div", (DOMAttributes) null, "Seconds Elapsed: ", this.state.secondsElapsed)); } }
The full code of this example is available there.
A second example transposes the simple todo list example given there: https://facebook.github.io/react/. This example illustrates best how to build React apps with JSweet.
@Interface static class TodoListProps { TodoProps[] items; } @Interface static class TodoProps { double id; String text; } @Interface static class TodoAppState { @Optional TodoProps[] items; String text; } static class TodoList extends Component<TodoListProps, Object> { private Element createItem(TodoProps item, Double __, TodoProps[] ___) { return any(createElement("li", (HTMLAttributes) $map("key", item.id), item.text)); } @Override public Element render() { TodoListProps props = union(this.props); return any(createElement("ul", EMPTY, array(array(props.items).map(this::createItem)))); } } static class TodoApp extends Component<Object, TodoAppState> { TodoAppState state = new TodoAppState() { { items = new TodoProps[0]; text = ""; } }; public void onChange(FormEvent e) { this.setState(new TodoAppState() { { text = (String) e.target.$get("value"); } }); } public void handleSubmit(FormEvent e) { e.preventDefault(); TodoProps[] nextItems = array(state.items).concat(new TodoProps() { { text = state.text; id = Date.now(); } }); String nextText = ""; this.setState(new TodoAppState() { { items = nextItems; text = nextText; } }); } @Override public Element render() { return any(createElement("div", EMPTY, // createElement("h3", EMPTY, "TODO"), // SimpleExampleTodoApp.createElementFromClass(TodoList.class, new TodoListProps() { { items = state.items; } }), // createElement("form", new HTMLAttributes() { { onSubmit = TodoApp.this::handleSubmit; } }, // createElement("input", new HTMLAttributes() { { onChange = TodoApp.this::onChange; value = union(state.text); } }), // createElement("button", EMPTY, "Add #" + (this.state.items.length + 1))))); } }
This is where using JSweet to build react apps becomes interesting because it helps structuring the program and helps with typing. In that example, we defined a TodoApp component for the application, which uses a TodoList component. Note also that the model and the state of the application are neatly defined as interfaces: TodoListProps, TodoProps, TodoAppState.
JSweet already supports AngularJS 1.x, Knockout, IONIC, Boostrap and many others (see examples here). We are currently using these frameworks to build real applications and will provide more documentation in the future. Supporting React.js was a bit challenging because of the complexity of the API. It will continue to be improved, but the examples shown here demonstrate that it is already possible to build React apps in Java with JSweet. I hope that you will enjoy it!