Rendering react in Java

Before telling me that I am crazy, think about it again.

There are plenty of ways to use server-side rendering today, and the practice of sending displayable data through the network has proven to be efficient :
– rendering data logged in the same tool as for backend services
– less client business logic, less coupling between frontends
– multi-channel broadcast on your clients devices (mobile, desktop, television)
Besides, some frameworks are written on top of virtual machines to do the job. Which one specifically ? Node.js

Java has a poor support today, and many companies are considering rewritting their stack just for the dream of going multi-channel.
Some others cannot, … well it is quite expensive.

While the idea of going rogue and starting to rewrite the app seems like the only way to go, there is an intermediairy solution for companies that mainly has code written in Java :
Make your Java api return react elements.

Howto ?
React components are just initiated (or can always be summarized) by a composition of two elements :
– a type
– properties
If there are some advanced features (forward references, lazy loading, derived states, react portals), these can always be wrapped in simple components in the javascript / typescript client.
Example of json that you can write in your http response :

{"type":"table","props":{"id":"test","className":"my-table","children":{"type":"tablerow","props":{"vAlign":"center","children":{"type":"tablecell","props":{"children":{"type":"MyText","props":{"big":true,"bold":true,"danger":false,"children":"Hello"}}}}}}}}

Then this is how an user interface can be sent through a rest endpoint :

@Component
class RenderText {

    fun page() = tag { table }.new {
        id = "test"
        className = "my-table"
    }.setChildren {
        tag { tr }.new { vAlign = "center" }.setChildren {
            new { td }.setChildren {
                MyLib.MyText::class.new {
                    big = true
                    bold = true
                    danger = false
                    children = "Hello"
                }
            }
        }
    }.toString()
}

Of course, just like in javascript, this is programmatic, you can embed whatever logic you want.
To be able to build this above, you need to declare your react components interface in the JVM.

package com.mycompany.service.react

object MyLib {

    interface MyTextProps: React.Props<MyTextProps> {
        var bold: Boolean?
        var danger : Boolean?
        var big : Boolean?
    }

    interface MyText : React.FC<MyTextProps>
}

You may now ask how can we have the react typings without leaving the JVM. In my case I have used (https://github.com/Kotlin/dukat) to convert the react typescript types back into Kotlin (and then back to the JVM)
You can just copy / paste react 16.13.1 types in kotlin : https://gist.github.com/libetl/7f4784eeaa5320b14b33567c0544c52a#file-react-kt

Now that you have your components and the react library, there is one missing gap : how do I instantiate interfaces ?
I have added just a small set of helpers method to achieve this goal, by creating proxy instances.
These proxy instances are objects which can extend a complex interface without necessarily implementing all the behaviors.
That lets you bind data to your components without needing you to plug the events and interactions… since these will be lost during the serialization.
Just copy paste those “medium sized” builder methods : https://gist.github.com/libetl/7f4784eeaa5320b14b33567c0544c52a#file-helpers-kt.

Your react code in java should now compile… Enjoy some cheap server-side rendering in the JVM now.
That is it for the backend part. Your API is now able to render a react as json.
Next, you have to convert that json into react elements.
I have a javascript ES5 file to do that : https://gist.github.com/libetl/7f4784eeaa5320b14b33567c0544c52a#file-expand-react-json-js.

Finally, you need the html page to fetch the API that you have created and to transform the result into components :

<!DOCTYPE html>
<html lang=“en”>
  <head>
    <meta charset=“UTF-8” />
    <title>Test server side rendering</title>
  </head>
  <body>
    <div id=“root”></div>
    <script
      crossorigin
      src=“https://unpkg.com/react@17/umd/react.development.js”
    ></script>
    <script
      crossorigin
      src=“https://unpkg.com/react-dom@17/umd/react-dom.development.js”
    ></script>
    <script
      crossorigin
      src=“https://cdnjs.cloudflare.com/ajax/libs/promise-polyfill/8.2.0/polyfill.min.js”
    ></script>
    <!– my lib export an umd variable called “components” –>
    <script
      crossorigin
      src=“https://cdn.mycompany.com/libs/mylib.js”
    ></script>
    <script
      src=“https://cdn.jsdelivr.net/npm/whatwg-fetch@3.5.0/dist/fetch.umd.min.js”
      crossorigin=“anonymous”
    ></script>
    <script src=“expand-react-json.js”></script>
    <script type=“text/javascript”>
      window.addEventListener(“DOMContentLoaded”function (event) {
        // api name here is server-driven-ui, but call it your name
        fetch(“server-driven-ui”)
          .then(function (response) {
            return response.json();
          })
          .then(function (json) {
            ReactDOM.render(
              expandReactJson(Reactcomponents“”json),
              document.getElementById(“root”)
            );
          });
      });
    </script>
  </body>
</html>

That is it,
And let’s be patient. Someday we are going to hear about Jetpack Compose for the web.

Until next time, goodbye.

Leave a Reply

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

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.