Porting a ReactJS Timeline to KotlinJS

I am learning how to work with Kotlin Multiplatform in a real world environment, which includes making websites with Kotlin/JS. I am all thumbs when it comes to CSS and have never done much with React. So a good way to really plow through that is to take concepts I want to replicate and then port them to Kotlin/JS (if possible). One UI feature that I’m exploring are formatted lists. We use them everywhere nowadays. Searching around I found this timeline example created by Florin Pop. It’s not a huge amount of code but it makes a pretty neat looking timeline view of a collection:

A timeline view in ReactJS
The original ReactJS timeline from the Florin Pop's example


Questions I want to answer are:

  • Can I reproduce this using Kotlin/JS?
  • Is the source code clearer or more obtuse in Kotlin/JS?
  • What does the generated Kotlin code look like?

First, why bother with KotlinJS? For me it’s part of my wanting to be able to use the same language for all my deployment targets: server, web, desktop, mobile. Kotlin’s multiplatform offerings gives me the possibility of doing that to a great extent. In a separate experiment which I’m hoping to document I’m trying to prove how well this works in practice. With the exception of a little bit of the view layer written in Swift for iOS, it is possible to literally write all of my targets in Kotlin. Along with unified language it allows for lots of shared code across the two. Second, I hate JavaScript. I code in it and use it as necessary but I prefer not to use it. I could pick up TypeScript and use that instead but since I’d be writing the back end, business logic, etc. in Kotlin anyway I figured why not write the Website in it too. All of this is predictaded on it being practical and reasonable though.

Let’s look at the basic structure of the ReactJS application from the top level down. You have a React “Timeline” top level component as the basis for the application:

const Timeline = () =>
    timelineData.length > 0 && (
        <div className="timeline-container">
            {timelineData.map((data, idx) => (
                <TimelineItem data={data} key={idx} />
            ))}
        </div>
    );

timelineData is an array of JSON objects populated elsewhere that looks something like this:

[
    {
        text: 'Wrote my first blog post ever on Medium',
        date: 'March 03 2017',
        category: {
            tag: 'medium',
            color: '#018f69'
        },
        link: {
            url:
                'https://medium.com/@popflorin1705/javascript-coding-challenge-1-6d9c712963d2',
            text: 'Read more'
        }
    },
    {
        // Another object with data
    }
];

The control creates a div which will create a new TimelineItem for each of the elements in the data structure passing in the data for that element and the index. What does the TimelineItem React control look like? It also is pretty simple:

const TimelineItem = ({ data }) => (
    <div className="timeline-item">
        <div className="timeline-item-content">
            <span className="tag" style=>
                {data.category.tag}
            </span>
            <time>{data.date}</time>
            <p>{data.text}</p>
            {data.link && (
                <a
                    href={data.link.url}
                    target="_blank"
                    rel="noopener noreferrer"
                >
                    {data.link.text}
                </a>
            )}
            <span className="circle" />
        </div>
    </div>

For each element we’ll be creating some nested divs using the information in the data to fill out the text, links, and colors. We are leveraging CSS classes to style each of these elements as well. The full CSS from Florin’s original example can be found here. Let’s look at the same laydown in the KotlinJS React code. First we’ll start with the KotlinJS React timeline component:

class Timeline: RComponent<TimelineProps, RState>() {
    override fun RBuilder.render() {
        div("timeline-container") {
            props.articles.forEachIndexed { idx, curArticle ->
                timelineItem {
                    article = curArticle
                    index = idx
                }
            }
        }
    }
}

There is a bit more annotation/decorating around the core but you get essentially the same concept as in the JavaScript version. Importantly this is all Kotlin code and code constructions, from the forEachIndex method on the list of articles to the on-the-fly created DSL of our “timelineItem”. This being able to on the fly create initialization syntax is something I think can help with code readability. Let’s look at the TimelineItem definition in KotlinJS now:

class TimelineItem: RComponent<TimelineItemProps, RState>() {
    override fun RBuilder.render() {
        div("timeline-item") {
            div("timeline-item-content") {
                key = "${props.index}"
                styledSpan {
                    css {
                        +"tag"
                        backgroundColor = props.article.category.color
                    }
                    +"${props.article.category.tag}"
                }

                time { +"${props.article.date.toDateString()}" }
                p { +props.article.text}
                a(href=props.article.link.url){+props.article.link.text}
                span("circle") {}
            }
        }
    }
}

Again, the structure looks pretty comparable. You can see some of the mixture of DSL and properties which we are using. You can see some of the string interpolation. The styledSpan item for styling div’s on the fly is a little more verbose but the code reads pretty well. The one thing we have that JavaScript didn’t have were data classes specifically defining our hierarchy. So whereas you had a straight up initialization of the JavaScript array our comparable Kotlin code looks like:

data class Article(
    val text:String,
    val date:Date,
    val category: Category,
    val link:Link)

data class Category(
    val tag:String,
    val color:Color,
)
data class Link(
    val url:String,
    val text:String)

enum class Categories(val category:Category) {
    Linux(Category("Linux", Color.blueViolet)),
    Mac(Category("Mac", Color.gold))
}

fun DefaultArticles():List<Article> = listOf(
    Article(
        text = "Rocky Linux 1st release",
        date = Date(2020, 12, 24),
        category = Categories.Linux.category,
        link = Link("https://www.zdnet.com/article/rocky-linux-first-release-is-coming-in-q2-2021-say-developers/", "Rocky Mountain Linux Link")
    ),
    Article(
        text = "Will your laptop run Big Sur?",
        date = Date(2020, 12, 22),
        category = Categories.Mac.category,
        link = Link("https://www.msn.com/en-us/news/technology/macos-big-sur-compatibility-will-your-laptop-work-with-the-new-os/ar-BB15QrZ2?stream=world", "MSN link")
    )
)

This is one of those personal preference things. Do you like to have to explicitly layout your classes like this or not. It’s possible to work with key/value pair JSON data if one needs to but when you know the structure and are expecting certain things I like it to be spelled out. It makes it so I don’t have to worry about being passed a string that doesn’t map to a Color, for example, or being given the Category data in some way I wasn’t expecting. The compiler and the JSON parser will be what catch those sorts of errors not the runtime. So overall doing this in Kotlin was really not that much different than in ReactJS except that I can use Kotlin and if I had other backend code I’d be able to reuse some of it on my web app too. What about all those styles that were defined?

Kotlin has a CSS wrapper as well called kotlin-styled. What does that look like? Well let’s take the top level CSS for this app:

.timeline-container {
    display: flex;
    flex-direction: column;
    position: relative;
    margin: 40px 0;
}

In Kotlin code that looks like:

object TimelineStyles: StyleSheet("TimelineStyles", isStatic = true)  {
    val timelineContainer by css{
        display = Display.flex
        flexDirection = FlexDirection.column
        position = Position.relative
        margin(40.px, 0.px)
    }

Again, Kotlin is using strongly typed parameters and values so that you don’t have some typo somewhere that can be caught at compile time that is not. JavaScript linters can do that too of course. So what can you do with the Kotlin CSS? Pretty much everything, almost all of it with strong typing. However when I tried to transliterate the code from actual CSS to this Kotlin Styled CSS I couldn’t match the same output. Ultimately I therefore went with just using the regular CSS and calling it from within the Kotlin code. The good news is that it easy to swap back and forth between the two within the same code so you don’t need an all or nothing approach. There can be a second reason to stick with regular CSS though, which was the drastically increased verbosity for the interspersed styling. To use these strongly typed styles “properly” one needs to use the styledDiv (or equivalent components which support the css sub-fields). That leads to something like:

styledDiv{
  css {
    +MyStyles.style
  }
  ...
}  

At the top of a big block it’s no big deal. However in another project I had the use of the styles/divs one right after another. So in “normal” code it reads like:

div("style1") { ... }
div("style2") { ... }
div("style3") { ... }

Where the code in the div was very brief as well. To use the strongly typed styles exploded the code to read like:

styledDiv{
  css {
    +MyStyles.style1
  }
  ...
}  
styledDiv{
  css {
    +MyStyles.style2
  }
  ...
}  
styledDiv{
  css {
    +MyStyles.style3
  }
  ...
}  

If I had to make additional CSS changes on the fly there then I could maybe see the point of using the extra syntax but this was just unnecessarily verbose with no short hand version along the lines of styledDiv(MyStyles.style){} In that case I decided to just go with strongly styled CSS but using their string equivalent in a normal div, so div("MyStyles-style1") which defeats part of the purpose of using strongly typed styling in KotlinJS in the first place.

So what do I think of KotlinJS after this experiment? It produced pretty clean HTML and JavaScript code, which can be debugged pretty easily. When compiled in production mode the total site code was about 700 KB, which is essentially the smallest size you can get a KotlinJS system. That is about 200 KB larger than the equivalent ReactJS site doing the same thing. Neither size is great for a small site but there is a lot of bootstrapping that goes into those base several hundred KB. With many graphics and other files weighing in comparably on even a simple website it’s pretty reasonable. In terms of the coding experience, being a Kotlin developer I liked it a lot. In this case it seemed to be a bit more verbose than the ReactJS code but in some places more readable. In an app with business logic I’d have the advantage of being albe to share Kotlin code between front and back end though. Unfortunately since some of my beef with web front end programming is around the machinations around CSS/HTML5 and this is essentially built on top of all that I didn’t get away from it. From that perspective things just didn’t get any better either way. I don’t think this exercise would convert any pure front end coder that loves JavaScript. For me though it made me more excited abotu experimenting with Kotlin multiplatform including it’s use making websites with KotlinJS



Picture of Me (Hank)

Categories

Updates (126)
Journal (119)
Software Engineering (115)
Daily Updates (84)
Commentary (68)
Methodology (58)

Archive

2021
2020
2019
2018
2017
2016
2015
2014
2013