Hi, I have trouble figuring out how to use React R...
# javascript
i
Hi, I have trouble figuring out how to use React Router. It doesn't provide any ways to implement RouteObject and it's a sealed interface, so I cannot provide them on Kotlin level. So I have to resort to
js()
and it's confusingly doesn't work. Details in 🧵
So I define my main component like this `
Copy code
val App = FC<Props> { ... }
Then I define router like so `
Copy code
val browserRouter = createBrowserRouter(arrayOf(js("""{ path: "/", element: App.create() }""")))
And the problem is that it says "App is not defined", and when I look at the generated code my
App
val is renamed to
App_0
So it looks like kotlin generates a separate name for it because it sees
App
in js code, but how then do I refer to the variables from the js block?
I see this in the doc "You can safely refer to local variables of calling function (but not to local variables of outer functions), including parameters." so do I have to wrap it in a function call and take App as a parameter?
a
so, not sure about my answer here, but if I understand correctly
val App = FC<Props> { ... }
defines a KT object, but
element: App.create()
expects a JS object but App is referred by App_0 in the JS generated code
i
Yeah. So wrapping it around in a function helped
Like this
Copy code
fun browserRouter(app: FC<*>): Router {
    js("console.log(app)")
    return createBrowserRouter(arrayOf(js("""{ path: "/", element: React.createElement(app) }""")))
}
🙌 1
Apparently global vals are not accessible from js() blocks
But now I am stuck at actually accessing the React stuff, as that is also unavailable
Basically it looks like currently this wrapper is unusable
We need a native kotlin route object
Like even if this worked, that's not a way to write code. I would need a lot of this route building
a
What if you will define an external interface?
Copy code
external interface BrowserRouterOpts {
  val path: String
  val element: ReactElement // <- not sure about the type
}


fun browserRouter(app: FC<*>): Router {
    return createBrowserRouter(object : BrowserRouterOpts {
       override val path = "/"
       override val element = React.createElement(app)
    })
}
i
createBrowserRouter requires RouterObject
Which is a sealed external interface 🤷
So I cannot extend or implement it
I think it's a generated code issue.
So, that kind of external interface is already there, it's called
RouteObject
and it's unusable
e
Which is a sealed external interface
Maybe @turansky knows why a lot of types are sealed, that's also something I was wondering (https://github.com/JetBrains/kotlin-wrappers/blob/d9346f3a825e53efbc64465faedb0efc[…]n-react-router/src/jsMain/generated/react/router/RouteObject.kt)
t
RouteObject
creation example is here
r
haha i was just about to type up a
jso
example
t
In normal case you don’t need unsafe
js()
call. We generate declarations to provide strict interoperability. If you see only hacky way how to solve your problem - you can write in this channel - we will provide strict solutions 🙂
FYI
Copy code
// type parameter is redundant
val App = FC<Props> { ... }

// expected call
val App = FC { ... }
i
Ok, I didn't know about that function
r
I'll add - one reason people might miss the
jso
function is because its only included in one of the wrapper libs
I throw the "kotlin-wrappers:kotlin-extentions" lib on all my js projects now out of habit
i
I would ofc prefer to be able to provide the actual type, as all of the type checking is skipped in this case
Ah, it's not
t
kotlin-extentions
is deprecated and doesn’t contain
jso
.
r
doh! what's the more appropriate way to import
I must be getting a downstream dependency
i
Ok, how does that function work
Looks like magic a bit
t
jso
is available in
kotlin-wrappers:kotlin-js
. You will receive it transitively with any other wrappers dependency.
👍 1
r
jso implementation:
Copy code
inline fun <T : Any> jso(): T =
    js("({})")

inline fun <T : Any> jso(
    block: @JsoDsl T.() -> Unit,
): T =
    jso<T>().apply(block)
Basically it works the same way the react props builders work - it lets you typesafely assign the vars given the appropriate T
I assume the reason its an "external sealed interface" is because they're not true Typescript interfaces, but merely Typescript types, which means you don't actually implement them persay
i
Yeah, I see it now. Well, would be nice to advertise this function more, I'd say
👍 1
Thanks for the solution 🙌
r
hm now I'm daydreaming about a KSP annotation that will generate a factory function for external sealed interfaces... 😅
off topic, but would be fun to write.
i
Just a shot in the dark, but any idea what's wrong with this now:
Copy code
fun main() {
    val container = document.getElementById("root") ?: error("Root element not found")
    createRoot(container).render(RouterProvider.create { router = browserRouter })
}

val browserRouter = createBrowserRouter(arrayOf(
    jso {
        path = "/"
        Component = App
        ErrorBoundary = Err
    }
))
It says "Matched leaf route at location "/" does not have an element or Component." on loading and obv doesn't render anything.
r
I haven't been using the Component argument myself (currently using element) but it sounds like whatever its receiving is either producing a null, undefined, or something it didn't expect
i
Hmm, it's basically the same as in the linked example. Also might be that the route is actually not registered for some reason, I guess
I am not a frontend dev, so this is all very new and I don't know anything and debugging is not great
So the route is registering, I put an element there and it renders
👍 1
But why doesn't Component work is a good question now
r
you've probably already found it, but you're in JS territory now so the library docs will probably be useful https://reactrouter.com/en/main
i
Yeah, looking at those as well
r
looks like they vaguely discourage using the Component prop in there since its working, i'd just stick to the element style and go forward
i
Do you just do
element = App.create()
?
r
yeah, the
create
function either with a closure for prop-passing or without it works well
its the equivalent of the jsx
<App/>
i
Hmm, it tells me App is undefined
But it's right there
Do I need more hoops to jump?
r
that is concordant with what the route told you before 😅. initialization problem maybe?
i
Copy code
val browserRouter = createBrowserRouter(arrayOf(
    jso {
        path = "/"
        element = Test.create()
    }
))

val Test = FC {
    div { h1 { +"Test" } }
}
This fails
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
r
reading this very literally - you're eagerly initializing browserRouter before initializing Test at file-class load time. I bet if you flipped the order it would work
i
Oh no
Is it like that now
You are correct and that scares me
Fortunately I just need to make one internal website for admin purposes
r
usually that sort of problem resolves if you just put them in different files, because then the loader can figure it out
i
Right
Thanks!
r
np, have a great day
t
I use
Component
property, it works fine. Especially when you use one component for different pages.
👍 1
This fails
Which Kotlin version do you use?
I create router inside component, use
useConstant
hook can be used from
kotlin-react-use
i
I use 1.9.0.
So the fact that the ordering matters but it does compile looks a bit off
t
IR?
i
Yes
I guess it might be because the name is referenced inside jso builder, which generates a plain js object?
Like if I use the name anywhere in the "native" kotlin code it's fine
t
It looks like bug, probably it's already reported
👌 1
146 Views