Fork me on GitHub

React.js

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.

Clone the React.js example project

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.

Write a first simple React.js app

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).

Dynamic HTML generation

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.

Creating your own components

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.

Reacting to events

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.

Wrap up

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!

2 Comments

  • dav thorne says:

    Why do this ? You are loosing all the advantages of JSX ? Just learn javascript and React dude

    • Renaud Pawlak Renaud Pawlak says:

      I know JavaScript pretty well and use it almost every day in different frameworks. It is a great and fun language but I have seen companies building big applications with it and crying for help because the applications were getting really hard to maintain. It is one thing to be able to sketch cool apps very fast with small development teams, it is one other thing to build apps over years that involve lots of different developers and requires a stable env.

      JavaScript is great, and it is moving in the right direction, but it is moving fast and it is really hard to cope with all the ecosystem evolutions. If you use frameworks such as IONIC and you try to update your Node.js environment after a couple months, you will see what I mean. The Java development environment may look outdated to you, but it is rigorous and stable. It comes with typing and powerful tools (refactoring, dependency management, Maven…), which make it better when building long-term enterprise applications.

      Yes, you are loosing some of the advantages of JSX, but in a way, JSX is a patch to help developer being more efficient/productive, not more rigorous. JSweet is a syntax/typing layer on the top of JavaScript/TypeScript, so it is not at all bound to React.js and you can apply the same principles with other JavaScript frameworks. React.js is very peculiar because of JSX, which is not the case of other frameworks.

      So, in some projects I use JSweet to create a bridge between the front-end that is written in TypeScript (which supports JSX typing), and the back-end that is written in Java. That’s a good way to use every language for the part it is better at.

      But in general, I prefer to write the entire JavaScript application in Java using JSweet. Yes, I loose some cool features, but I have my reasons. At least two reasons:

      1. I get to manage my application modules with Maven (JSweet artifacts – candies)
      2. I get full instant typing/refactoring in my Java IDE

      With JSweet I can safely refactor my JavaScript application consistently with my server (services and dtos are shared). Hence my customers can ask me for last minute modifications and I can feel confident saying “ok, let’s do it”. That’s priceless!

      As a conclusion, I would say that using JSweet, what you loose in the short term, you win it back (and much more) in the long term. It is far less easy to learn Java/Maven/JSweet than learning JavaScript/React.js because the latter is designed to be easy to learn and use.

      It is much harder to build a house of bricks, but it lasts longer than a house of straw!

      DISCLAIMER: In the end, we all need to be good programmers and smart enough to make the best choices depending on our constraints/context. There is no unique/universal solution. There are choices to be made everyday depending on the context. Sometimes I use JavaScript directly, sometimes I use JSweet. It depends. The more alternatives we have, the more likely we are to make the right choices.

Leave a Reply

Your email address will not be published. Required fields are marked *