Introduction to Kotlin and React

React components via Kotlin can be developed in several ways.

Class Method

The class method is the very common Babel method you will see.

class MyCustomComponent extends React.Component {
    render() {
        <h2>MyCustomComponent</h2>
    }
}

With Flow typing Kotlin and Babel look strikingly similar.

type MyCustomComponentProps = {
    tile: string
}

type MyCustomComponentState = {
    clicked: boolean
}

class MyCustomComponent extends React.Component<MyCustomComponentProps, MyCustomComponentState> {
    ...
}

A base component without any custom state or props.

class MyCustomComponent : RComponent<RProps, RState>() {
        override fun RBuilder.render() {
            h2 { +"MyCustomComponent" }
        }
}

If we wanted to declare custom props, instead of a flow type. We utilize an interface. These interfaces extend the RProps/RState, interfaces. These provide basic React properties for your class.

interface MyCustomComponentProps : RProps {
    title :  String
}

interface MyCustomComponentState : RState {
    clicked : Boolean
}

class MyCustomComponent : RComponent<MyCustomComponentProps, MyCustomComponentState>() {
        override fun RBuilder.render() {
            h2 { +"MyCustomComponent" }
        }
}

Did you see the extension function on RBuilder?. Any react elements must extend off of RBuilder dsl. There are times where we utilize the class like in the hash router. But if we wish to use this in a render function, then it needs to extend off of the RBuilder.

Without Custom Props

fun RBuilder.MyCustomComponent() = child(MyCustomComponent::class) {
}

With Custom Props

  • Via property declaration
fun RBuilder.MyCustomComponent(title: String = "My Custom Component) = child(MyCustomComponent::class) { props ->
    attrs.title = title
}
  • Via Closure
    • Note that we change the paramater so we don't have variable name collision.
fun RBuilder.MyCustomComponent(inTitle: String = "My Custom Component) = child(MyCustomComponent::class) { props ->
    attrs {
        title = inTitle
    }
}

Wait what is child doing?

    fun <P : RProps, C : Component<P, *>> child(klazz: KClass<C>, handler: RHandler<P>): ReactElement {
        val rClass = klazz.js as RClass<P>
        return rClass(handler)
    }
    
    typealias RHandler<P> = RElementBuilder<P>.() -> Unit

So we're taking in a Kotlin Class, hence the ::class. Taking the JavaScript implementation and casting it to a React Class. The handler sets the properties of the inbound item.

Stateless Function Method

A standard non flow arrow function component.

const MyCustomComponent = (props) =>
  <h2>My Custom Component</h2>;

In flow, with a custom props.

type MyCustomComponentProps = {
  title: number
};

function MyCustomComponent(props: Props) {
  return <h2>My Custom Component</h2>;
}

A component without any custom props.

fun RBuilder.MyCustomComponent(children: react.dom.RDOMBuilder<kotlinx.html.Tag>.() -> kotlin.Unit): react.ReactElement {
    return h2 { +"My Custom Component"}
}

We can clean this up with type aliasing.

typealias ReactChildrenHandler = react.dom.RDOMBuilder<kotlinx.html.Tag>.() -> kotlin.Unit

fun RBuilder.MyCustomComponent(children: ReactChildrenHandler): react.ReactElement {
    return h2 { +"My Custom Component"}
}

This is only if we need children components. i.e.

<MyComponent>
    <MySubComponent/>
</MyComponent>

Without any child elements, i.e.

fun RBuilder.MyCustomComponent(): react.ReactElement {
    return h2 { +"My Custom Component"}
}

A component with a custom title.

fun RBuilder.MyCustomComponent(title: String, children: ReactChildrenHandler): react.ReactElement {
    return h2 { +"My Custom Component ${title}"}
}

Quick Review

We don't use < in Kotlin. Instead all components are based off of closures. RBuilder is a domain specific language. Which allows us to nest these easily.

JSX

<h2></h2>

Kotlin

h2 { }

Props are passed in as arguments.

JSX

<h2 title="Test"></h2>

Kotlin

h2("Test") { }

Inner HTML must be called with a prefix of +.

JSX

<h2>My Custom Component</h2>

Kotlin

h2 { +"My Custom Component" }

Let's dive into that plus sign. It's calling the following.

open class RBuilder {
    ...
    operator fun String.unaryPlus() {
        childList.add(this)
    }
    ...
}

So in essence it's adding the text to the list of children items to be rendered.

Event Handlers

I recommend you start with my other post on this topic.

I've not determined a way to do this without a class based component.

In Line

class MyCustomComponent extends React.Component {
    render() {
        <button
            onClick={(e =>
                window.alert("Button pressed"
            )}
        >
            Create an Alert
        </button>
    }
}
class MyCustomComponent : RComponent<RProps, RState>() {
        override fun RBuilder.render() {
            button { 
                attrs.onClick = { event : Event -> 
                    window.alert("Button pressed")
                }
                +"Create an alert" 
            }
        }
}

External

class MyCustomComponent extends React.Component {
    buttonHandler(e) {
        window.alert("Button pressed"
    }
    
    render() {
        <button
            onClick={this.buttonHandler}
        >
            Create an Alert
        </button>
    }
}
class MyCustomComponent : RComponent<RProps, RState>() {
    private fun buttonHandler(event : Event) {
        window.alert("Button Clicked")
    }
    
    override fun RBuilder.render() {
        button { 
            attrs.onClick =  buttonHandler
            +"Create an alert" 
        }
    }
}

You can even move this outside of the class and make it very generic, and composable. Let's try stateless functions


const ButtonAlert = (props) =>
  (<button
    onClick={(e =>
        window.alert("Button pressed");
    )}
  >
    Create an alert
  </button>);
function buttonHandler(e) {
    window.alert("Button pressed");
}
const ButtonAlert = (props) =>
  (<button
    onClick={buttonHandler}
  >
    Create an alert
  </button>);
fun RBuilder.ButtonAlert(): react.ReactElement {
    return button { 
        attrs.onClick = { e : Event -> 
            window.alert("Button pressed")
        }
        +"My Custom Component"
    }
}

Something I do

When wiring a bunch of nested components together. I leave the state largely in the parent component. This may be a bad practice but I saw it via several stack over flow discussions. So willing to hear alternatives. Anyway the idea is we pass down functions to handle onClick calls, then modify the master state. I usually have an overaching state descending from my page.

Example Component Hierarchy.

User Settings Page (UserSettingsState)
    Personal Information Form Component (State of UserSettingsState.PersonalInformation)
    Friends List Component (State of UserSettingsState.FriendsList)
    ...

So we assume that personal information is a form derived field. Taking in a lot of information, and state we want to maintain. When we render each text field, selection, radio button, etc. We pass in a function that will handle the change, and render state in the parent component.

import org.w3c.dom.events.Event

typealias EventHandlerFunction = (Event) -> Unit

interface PersonalInformationFormState {
    name: String
}

class PersonalInformationForm : React.Component<RProps, PersonalInformationFormState> {
    
    private fun nameChangeHandler(e: Event) {
        val target = event.target?.asJsObject().unsafeCast<HTMLInputElement>()
        setState {
            name : target.value
        }
    }
    
    override fun RBuilder.render() {
        NameField(nameChangeHandler, value=state.name) { }
    }
}

interface NameFieldProps {
    nameChangeHandler : EventHandlerFunction
    value: String
    disabled : Boolean
}

class NameField extends React.Component<NameFieldProps, RState> {
    render() {
        input {
            attrs.onChange = nameChangeHandler
        }
    }
}

fun RBuilder.NameField(inNameChangeHandler : EventHandlerFunction,
    inValue : String = "Please enter your name",
    inDisabled : Boolean = false) = child(NameField::class) {
    attrs.nameChangeHandler = inNameChangeHandler
    attrs.disabled = inDisabled
}

So here we a parent component that wraps everything of Personal Information. This contains several fields, but we're just showing name. It has a state where name is a String.

The input field for name takes in a nameChangeHandler which is an event handler function. Disabled which is optional. The idea is with disabled the form will be locked until the user clicks edit. This is simply to highlight keyword argument support. Lastly we have value which provides a default, but we will override.

So whenever you start entering text in the field. The event handler will fire. Updating the state, then updating the component with the new value.

*I will continually update this as I learn more about React with Kotlin