React in Kotlin/JS, What I learned (long but useful read)

Hi everyone!

Note: Hopefully I can post my entire project later, but as it’s a university project it would be considered fraud to publish it :confused:.

Why React?
Recently for University, we had to make a visualization tool for a dataset of our choosing. One of the recommendations was to use d3.js, because of the diversity in graphs. Now I don’t like JavaScript; it’s very unpredictable unless you’ve been working with it for years and the best way to debug is just console.logging everything you do. So when I found out Data2Viz existed as d3 alternative for Kotlin, I decided to give it a shot and try to do this project in Kotlin/JS (I did choose for JS because showing the project on a website is still easier than running a Java program). Frameworks are not really necessary using Kotlin/JS but due to the current state of it, if you want a nice layout, you’re bound to reuse JS libraries. I worked with React (native) before and I liked the state management, so when I found that Jetbrains made their own kotlin-react wrapper, I thought it was worth a shot.

build.gradle.kts
Kotlin React tutorials like this are great to get some info about how React works in Kotlin, however, they all still use create-react-app-kotlin-app as a starting point, which uses npm only, so Gradle-using libraries are off limits. Now, Jetbrains is rewriting these as we speak, but in my case, I had to find it all out myself using only this official tutorial for setting up a JS project. .kts examples of build.gradle files are even harder to find, so it was a fun challenge.
Anyways, this was my final build.gradle.kts, (quick note: to use eap versions of kotlin, you need to add this to your settings.gradle.kts). The project uses webpack automatically to enable hot reloading when saving a file. To do do this, you need to run the project like gradlew browserRun -t or setup the run configuration similarly.
Webpack hot reloading didn’t work flawlessly right away though. I described this in an earlier thread. The solution appeared to be adding ./webpack.config.d/webpack.config.js to the root of my project. After this, it worked perfectly!

Layout
I am a fan of material design, not gonna lie. A React library that is oftentimes used for creating material UI’s is, well, Material-UI. Lucky for us, there are several Kotlin wrappers for this library. The first one I found was Muirwik. It worked great with kotlin-styled, so I decided to use it. It is not finished yet (like most things Kotlin/JS), so instead of importing it as a library, I was required to just download the files into a project folder so I could change some stuff when needed.
A simple component I created using the Card of Material-UI actually was a card that increased its shadow when the cursor hovers over it. It can be used just like a normal mCard. Check it out here! (It does use my React Prop and State Delegates, but it can easily be rebuilt without it).

React tips #1: Passing functions to child components
The age-old React rule of not defining arrow functions / lambdas inside attributes also applies to Kotlin. You might be tempted to write an onClick like this:

override fun RBuilder.render() {
    childComponent {
        attrs {
            onClick = { it: Event ->
                counter++
            }
        }
    }
}

While this avoids clutter, React doesn’t like it. This is because components (especially when they’re pure components) always refresh when their props / state change. According to Kotlin/JS
{ it: Event -> counter++ } != { it: Event -> counter++ }, so everytime the parent component updates, the child will update as well, even though nothing essentially changed.
So alright… like this then?

private fun doOnClick(it: Event) { counter++ }    
override fun RBuilder.render() {
    childComponent {
        attrs {
            onClick = ::doOnClick
        }
    }
}

Yeahh… no. Unfortunately Kotlin/JS works differently than Kotlin/JVM in the sense that in Kotlin/JS ::doOnClick != ::doOnClick.
So, the way to go is like this:

private val doOnClick: (Event) -> Unit = { counter++ }
override fun RBuilder.render() {
    childComponent {
        attrs {
            onClick = doOnClick
        }
    }
}

Unfortunately this means in some cases you’ll need something like this, if you want to give an onClick extra information:

private val allDoOnClicks = hashMapOf<Item, (Event) -> Unit>()

private fun doOnClick(item: Item): (Event) -> Unit =
    allDoOnClicks.getOrPut(item) { // save each onClick function with item as key to prevent unnecessary reloads
        { it: Event ->
            // do something with this item
        }
    }

override fun RBuilder.render() {
    for (item in items)
        childComponent {
            attrs {
                onClick = doOnClick(item)
            }
        }
}

Another way this might work is using a more React-based solution is using useMemo. Fair warning, I haven’t fully tested it yet, but feel free to let me know whether it works better:

private fun doOnClick(item: Item): (Event) -> Unit = { it: Event ->
        // do something with this item
}
    

override fun RBuilder.render() {
    for (item in items)
        childComponent {
            attrs {
                onClick = useMemo({ doOnClick(item) }, arrayOf(item))
                // or maybe even:
                onClick = useMemo({{ it: Event -> /* do something with item */ }}, arrayOf(item))
            }
        }
}

In theory, this should only call doOnClick and thus regain a new (Event) -> Unit function for onClick if item changes, otherwise it takes it from memory.

React tips #2: Component class definition
This is a small tip, but it did cause me some trouble. When defining a class for an RComponent, do not name the props in the constructor props:

class ExampleComponent(prps: ExampleComponentProps) : RComponent<ExampleComponentProps, ExampleComponentState>(prps) {}

See, prps is not the same as this.props or just props when the component updates because of a changing attribute. If you only use this.props you’re fine as you’ll always get the newest version of props for this component. this.props and this.state should be seen as functions getProps and getState. If you keep that in mind, you’ll be fine.
Bonus tip: when using a component with properties / attributes, make sure you use the right init function, even if you’re not using props inside the init function:

override fun ExampleComponentsState.init(props: ExampleComponentProps) {
    counter = 0    
}

React tips #3: Kotlin React Prop and State Delegates
This is a couple of functions / classes I wrote to Kotlin-ify React state and props a bit more using delegates. It’s not for everyone as it requires some more writing in some parts but less in other parts. You can read my forum post here.

Data2Viz in React
Like I said in the intro, I used Data2Viz in this project. D3.js and Data2Viz however need to bind to an HTML canvas element, which requires some ref-magic in React. If you ever want to use my solution for yourself, you can find it here. Unfortunately Data2Viz doesn’t allow the JS variant to use the SVG way of creating graphs/maps, only Canvases. Those don’t work well with cursors, only providing you with (x, y) coordinates. D3 devs have found a solution for this by using a hidden canvas and a lot of different colors (as used here), so that’s why my component function also has an optional hidden canvas you can run stuff on.

Porting over JS libraries
Adding a npm package is super easy using gradle. For example, you can just type implementation(npm("react-list")) in the dependencies and it will get imported. Getting it to work with Kotlin is a bit more difficult. I got to port over one library: react-list.
I took examples from kotlinlang.org and their react tutorial.
I don’t know that the best way is to make it into a library or official wrapper or something, but you can find my version of react-list for Kotlin here (This also includes some helper functions I borrowed from Muirwik to more easily create a styled component).

Multithreading is too hard for now
Because of my background in Android development and I was performing a large amount of calculations on a dataset (freezing the UI) I was looking into multithreading. As we have Coroutines in Kotlin/JS I assumed it just would work. I was very wrong. JavaScript is single threaded. All coroutines do is provide async functions. This means that the function can be executed at any time, in any order, but still on the main thread.
I found out about Web Workers for JavaScript. There unfortunately isn’t any (great working) Kotlin library which uses this, nor would this be an easy task I think. See, the main way to start a worker in javascript is like Worker("worker.js"). Kotlin, in my case, puts all the code in a single .js file, so this is a no-go. You can also use an in-line Worker in JS by passing plain JS as a String to a blob and creating a worker like that. Again, this is a problem in Kotlin, because Kotlin has to compile to JS first. Passing objects from and to workers from another thread is limited to a few native types and sending an object reduces it to just its attributes (breaking HashMaps etc). If you’re okay with these limits, you can try the Kworker library, which is difficult to use, however it does work.
Libraries that make Workers easier to use in JS like comlink are great, but almost impossible to port to Kotlin due to the hacks they use to make it work.
So yeah, someone with a lot of time and knowledge of both Kotlin and JavaScript needs to take a deep look into this as in 2020 a system that can’t even multithread properly shouldn’t exist.

Conclusion
I’m amazed Kotlin is able to be so diverse. While IMO it’s definitely better suited to work in the JVM, the JavaScript variant could be the typed refresh JavaScript needs. TypeScript is great, but it’s trying to make the meal that is JS only tastier by adding a lot of sauce. Kotlin just threw out the chef and is making the meal themselves in their kitchen (maybe I shouldn’t write this at dinner time). Now obviously, this is only my opinion and I know people that really like the way how JS/TS works, but for all those students (like me) that were taught Java as their first language, Kotlin makes a whole lot more sense.
With the promise of a single language to cover all, I think React shouldn’t be ignored by Kotlin. It is great to see Jetbrains creating kotlin-react wrappers, but I think we need a lot more people and an easy way to post wrappers to make it more mainstream.
I’m excited for Dukat merging into the build process for Kotlin/JS soon, allowing you to import d.ts files from NPM and use them like it’s Kotlin. However I don’t know how this will work for libraries that are built in TypeScript and do not use type definitions, but we’ll see.

If you made it through all this you must also be very dedicated. Let me know what you think about React and / or Kotlin/JS and if you have any other tips for me or for others, don’t hesitate to post them below!

16 Likes

I’ve never used Kotlin in the JavaScript ecosystem, but it all sounds like two strangers were working together. The main problem I see is the lack of support, since wrappers for all kinds of libraries are needed. Build tools are the next thing - nobody is using Gradle in the JS world. Then there is not much public knowledge (read: Stackoverflow answers and blog posts) about Kotlin + JS.

All in all I would say that TypeScript is a much better fit if one wants to use JS stuff in a typesafe way.

A real game changer could be somthing like Microsofts Blazor, but for Kotlin. A framework built in Kotlin to be used with Kotlin and Kotlin libraries that gets compiled to webassembly.

2 Likes

Great text, I think you should make an article from it.

1 Like

I agree, it feels a bit odd as of now, but I think a lot can be resolved if a lot of people just try it and create wrappers/libraries for it. If Kotlin/JS were a language on its own it would surely fail, but due to Kotlin getting popular in the Android/Java space I think it has a chance. I think it will first start in NodeJS based servers though, as a way to provide a single codebase and the same business logic for both the app as the server. I think if Google for instance would decide to provide Firebase Functions with Kotlin as first language, just like Android, libraries and wrappers would come a lot faster.

I don’t think Gradle would be a problem as I think most people trying Kotlin/JS are coming from the JVM world anyways and especially with multiplatform Kotlin libraries, you kinda have to use Gradle. The npm/Gradle integration needs some work though…

Obviously as standalone framework would be great. One bright light is Multiplatform libraries. If those improve it doesn’t matter what platform you target as long as you use only those libraries. Imagine creating a single UI that works in web pages, android apps, iOS etc.

Sure why not, great idea!

You just had to put it behind the paywall :frowning:

2 Likes

Hahaha, nah, just don’t log in with medium when reading articles then you can read all you like.
And then again I’m not gonna remove it here, knowledge is supposed to be free :).

Thanks for the post!

I tried long and hard to get a painless, or at least as painless as possible, workflow with React and Kotlin, but eventually I just gave up. I wanted a JVM server, common domain logic code, JS frontend, but it was just too annoying with all the different approaches. I really enjoy React in the front-end, so I was sad to give it up. Maybe in the future it will get better.

Then I switched to pure Kotlin-MPP and for the web part I just started with a very basic MVP pattern. This worked brilliantly. I just use kotlinx-html for the elements, but mainly I am just using the browser and dom api’s combined with some good ol’ OOP and it works perfectly. It was a breeze to just create an efficient enough hierarchical component system. Routing? Just using the History API works fine.

3 Likes

Thanks for the post @Jolanrensen, I was wondering how did you use kotlin-styled in Muirwik components?

Muirwik already uses kotlin-styled out of the box. This means you can just go ahead and use

mButton {
    css {
        whateverYouWant = 5
    }
}

Oh I was not aware of that, that’s great. I’m actually trying to use rmwc instead of the Material UI lib as the former is a wrapper of the official material components library developed by Google and it provides more components out of the box, however, I’m finding it hard to make it work properly so I might have to try Muirwik.

Ah right, maybe there’s a kotlin wrapper for rmwc too! If not, muirwik worked well enough for me.

Great writeup!

I still kind of feel that it’s TypeScript, due to its structural type system, rather than Kotlin that suits JS better.

That’s true, however, the beauty of kotlin would be that the program could run on JVM, js and native with the same code, abstracting away from the js structure. This is different from js-only kotlin like I used this project, for that, typescript would probably be easier.

1 Like

Is there a link to the final repo for your project @Jolanrensen?

No unfortunately not, as it was a University project. Sharing it online would be considered fraud :confused:. So I guess I’ll have to wait a few months to be able to share it (and just not mention the course or the university in the repo, then I should be fine).

1 Like

Good to know much.