My company is looking to see if react kotlin/js is...
# javascript
s
My company is looking to see if react kotlin/js is a viable alternative to react/js. we built a demonstrator using react and tailwind and now the challenge is to convert to react kotlin/js. I seem to have run into a bit of a problem quite early on and I'm looking for some help please.
We are using the tailwind headless components to build our ui. https://github.com/tailwindlabs/headlessui
The first component I am trying to convert is the
Switch
component.
It also has a
Switch.Label
and
Switch.Group
which I can't expose to kotlin.
here's my code for the
Switch
Copy code
@file:JsModule("@headlessui/react")
@file:JsNonModule
import kotlinx.html.RP
import react.*
@JsName("Switch")
external val ReactSwitch: RClass<ReactSwitchProps>
external interface ReactSwitchProps: RProps {
    var checked: Boolean
    var className: String
    var onChange: (Boolean) -> Unit
}
b
This is an example for material-ui (another react components lib) https://github.com/subroh0508/kotlin-material-ui
s
Thanks I did have a read of the material-ui but really can't figure out how to get to the internal functions which are not exported in the headlessui library
b
They might not be exported directly, but must be reachable from public js domain
Otherwise you wouldn't be able to use them in js as well 😀
s
agreed, and this is what is so frustrating. the top lvel switch works fine in kotlin, but until i can figure out how to expose the internal components then i'm a bit stuck. most of the headless-ui lib uses the same technique. export a single function, but have other functions (not exported) which are bound to the main function.
b
A lot of js libs use this export pattern:
Copy code
const Comp1 = ...
const Comp2 = ...

// or module.exports = {...}
export default {
 Comp1,
 Comp2,
}
Which in kotlin can be represented as this
Copy code
@JsModule("mymodule")
external object AnyNameYouWant {
  val Comp1: RClass<RPRops>
  val Comp2: RClass<RProps>
}
s
at the bottom of the
Switch
file they do this:
Copy code
Switch.Group = Group
Switch.Label = Label
Switch.Description = Description
and those don't seem to be exported?
b
can you post a sample js code you're working with?
s
here is what I have so far:
Copy code
@file:JsModule("@headlessui/react")
@file:JsNonModule

import kotlinx.html.RP
import react.*

@JsName("Switch")
external val ReactSwitch: RClass<ReactSwitchProps>


external interface ReactSwitchProps: RProps {
    var checked: Boolean
    var className: String
    var onChange: (Boolean) -> Unit
}
this maps to `Switch`but I also need
Switch.Label
,
Switch.Group
and
Switch.Description
b
How about this the
Copy code
@JsModule("mymodule")
external object Switch {
  external operator fun invoke(props: RProps): RComponent<RProps>
  val Group: RClass<RPRops>
  val Label: RClass<RProps>
  Description: RClass<RProps>
}
Might need some adjustments, but hopefully you get the idea
s
cool, thank-you, i will give that a go 👍
b
Please post back the final working solution once you get there
👍 1
s
i've defined this :
Copy code
@file:JsModule("@headlessui/react")
@file:JsNonModule

import react.*

external object Switch {
    operator fun invoke(props: RProps): RComponent<RProps, RState>
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
however, when I come to use it
Copy code
val switch = Switch(props)
Uncaught TypeError: Switch.invoke is not a function
b
Operator function needs to be external too
s
i get this error when I make it external
Copy code
Non top-level `external` declaration
b
Otherwise you could try this
Copy code
external fun Switch(props: RProps): RComponent<RProps, RState>
external object Switch {
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
If the name clashes, then this might work
Copy code
external fun Switch(props: RProps): RComponent<RProps, RState>
external interface SwitchContainer {
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
@JsName("Switch")
external val SwitchC: SwitchContainer {
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
s
this compiles ...
Copy code
@JsName("Switch")
external fun Switch(props: ReactSwitchProps): RComponent<RProps, RState>
external object Switch {
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
🎉 1
b
But does it work? 😄
s
but doesn't produce the react component when i use it
Copy code
Switch.Group {
                Switch.Label {
                    +"Switch Label"
                }
                Switch(props)
            }
the complete function is
Copy code
val todoCard = functionalComponent<TodoCardProps> { props ->

    val (checked, setChecked) = useState(false)

    div("rounded overflow-hidden h-auto sm:h-96 border-solid border border-blue-900") {
        div("px-2 py-3 bg-skin-fill-primary h-auto lg:h-16 sm:flex items-center justify-between") {
            p("text-white text-2xl leading-7.5 text-left sm:uppercase sm:font-bold") {
                +"Todo for ${props.name}"
            }
        }
        val switchColor = if (checked) "bg-blue-600" else "bg-gray-200"
        val switchClass = "$switchColor relative inline-flex items-center h-6 rounded-full w-11"
        var switchButtonClass= if (checked) "translate-x-6 inline-block w-4 h-4 transform bg-white rounded-full" else "translate-x-1 inline-block w-4 h-4 transform bg-white rounded-full"
        div("p-10") {

            var props: ReactSwitchProps = jsObject {
                this.checked = checked
                this.className = className
                this.onChange = { setChecked(!checked) }
            }

            Switch.Group {
                Switch.Label {
                    +"Switch Label"
                }
                Switch(props)
            }
        }

        println(Switch)
        println(Switch.Description)
        println(Switch.Group)
        println(Switch.Label)

    }
}
b
You can always do this then (although it's ugly)
Copy code
import react.*

@JsModule("@headlessui/react")
@JsNonModule
external object Switch: RClass<RProps> {
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
s
thanks, i did try that as one of my earlier attempts but got this error
Copy code
Object 'Switch' is not abstract and does not implement abstract member
b
add external
s
sorry, this is what i have giving that error
Copy code
import react.*
@JsModule("@headlessui/react")
@JsNonModule
external object Switch: RClass<RProps> {
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
b
Ok, how's this then?
Copy code
import react.*
@JsModule("@headlessui/react")
@JsNonModule
external object Switch {
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}

operator fun Switch.invoke(props: RProps): RComponent<RProps, RState> = Switch.asDynamic()(props)
s
getting a different error now ...
with this:
Copy code
println(Switch)
println(Switch.Description)
println(Switch.Group)
println(Switch.Label)
console displays:
Copy code
[object module]
undefined
undefined
undefined
the closest so far is this
Copy code
@file:JsModule("@headlessui/react")
@file:JsNonModule

import react.*

external object Switch {
    operator fun invoke(props: RProps): RComponent<RProps, RState>
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
b
Does that one have Group, Label & Description as defined?
s
which with the console I get function Switch(props) function Description(props) function Group$2(props) function Label$1(props)
yep, all four defined 👍
and
Copy code
Switch.Label {
//
}
Switch.Group {
//
}}
work as expected
but can't get
Copy code
Switch {
}
to compile
b
Are you sure marking Switch itself as
RClass<RProps>
doesn't work?
Copy code
@file:JsModule("@headlessui/react")
@file:JsNonModule
import react.*
external object Switch: RClass<RProps> {
    operator fun invoke(props: RProps): RComponent<RProps, RState>
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
s
it comes back to a compile error
Object 'Switch' is not abstract and does not implement abstract member public abstract var contextType: RContext<Any>? defined in react.RClass
b
Have you tried this already
Copy code
@file:JsModule("@headlessui/react")
@file:JsNonModule
import react.*

external val Switch: RClass<RProps>
external object Switch {
    operator fun invoke(props: RProps): RComponent<RProps, RState>
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
Might have to play around with @JsName if kotlin complains about duplicate names
Copy code
@file:JsModule("@headlessui/react")
@file:JsNonModule
import react.*

external val Switch: RClass<RProps>

@JsName("Switch")
external object SwitchEx {
    operator fun invoke(props: RProps): RComponent<RProps, RState>
    val Group: RClass<RProps>
    val Label: RClass<RProps>
    val Description: RClass<RProps>
}
Wait, this should work
Copy code
import react.*
@JsModule("@headlessui/react")
@JsNonModule
external object HeadlessUI {
  external object Switch {
      val Group: RClass<RProps>
      val Label: RClass<RProps>
      val Description: RClass<RProps>
  }
}
operator fun HeadlessUI.Switch.invoke(props: RProps): RComponent<RProps, RState> = Switch.asDynamic()(props)
s
ok, your last snippet was looking promising as well
b
Yeah, both should work
when you use @file:JsModule, it maps files 1 to 1 (flattens), but when you use @JsModule on value or object, it maps it to that value or object (thus the need for extra nesting)
s
got to jump on a meeting, but this looks good. will update after meeting. many thanks 👍
b
good luck
s
had to make a slight tweak
Copy code
import react.*
@JsModule("@headlessui/react")
@JsNonModule
external object HeadlessUI {
    object Switch {
        val Group: RClass<RProps>
        val Label: RClass<RProps>
        val Description: RClass<RProps>
    }
}
operator fun HeadlessUI.Switch.invoke(props: RProps): RComponent<RProps, RState> = HeadlessUI.Switch.asDynamic()(props)
all the functions are defined 👍
but ... i can't get the call site to work 😞
Copy code
HeadlessUI.Switch.Group {
                HeadlessUI.Switch.Label {
                    + "Switch Label"
                }
                HeadlessUI.Switch() {

                }
            }
i get an unresolved reference for HeadlessUI.Switch
b
Didn't the first option work (with @file:JsModule)
Otherwise you could try this
Copy code
fun Switch(props: RProps): RComponent<RProps, RState> = HeadlessUI.asDynamic().Switch(props)
Or even making everything shut up
Copy code
import react.*
@JsModule("@headlessui/react")
@JsNonModule
internal external val HeadlessUI: dynamic
val Switch: RClass<RProps> = HeadlessUI.Switch
val SwitchGroup: RClass<RProps> = HeadlessUI.Switch.Group
// etc...
s
🎉
here's the code ...
Copy code
SwitchGroup {
                div("flex items-center") {
                    Switch {
                        attrs.checked = checked
                        attrs.className = switchClass
                        attrs.onChange = { setChecked(!checked) }
                        span(switchButtonClass) {}
                    }
                    SwitchLabel {
                        span("ml-4") {
                            + "This is a switch"
                        }

                    }
                }
            }
and the wrapper:
Copy code
import react.*
@JsModule("@headlessui/react")
@JsNonModule
internal external val HeadlessUI: dynamic
val Switch: RClass<ReactSwitchProps> = HeadlessUI.Switch
val SwitchGroup: RClass<RProps> = HeadlessUI.Switch.Group
val SwitchLabel: RClass<RProps> = HeadlessUI.Switch.Label
you've been a brilliant help
is there anyway i can buy you a beer or 2 through paypal?
b
Man I'm just as happy to get this solved! Always enjoy a good challenge 😄
s
thank-you - I'll be back 🤣 - you might regret it 😄
🧌 1
b
I've been thinking of creating a gist where I'd put all the million ways you can export js code and wrap that in kotlin (each case is different I've found...), but never got to it 😄
s
yeah, i'm finding the kotlin docs really hard. my developer skills are iOS / Swift and many years ago Java
b
I found that you need to know a lot more about js internals to do kotlin.js as compared to plain js 😄
s
you think we could get a tool that we can run against the JS lib and have the wrappers auto-generated
b
Have you heard of dukat? It's not perfect, but works in some cases
Although it will not link to other kotlin.js wrappers, but i usually use it as a starting point for hand-crafted declarations
s
yeah, i tried that but it didn't help 😞