Closed bbodi closed 3 years ago
In the kotlin-fullstack-example there is an
open class ReactExternalComponentSpec<P : RProps>(val ref: dynamic)
kotlin-fullstack-example uses their own fork of what kotlin-wrappers were before they actually were a thing =)
Now you can do this:
// it assumes that `const ReactSomething = require('react-something')` works in JS
@JsModule("react-something")
external val ReactSomething: RClass<dynamic>
// then somewhere inside your components
ReactSomething {
attrs {
foo = "bar"
}
// will be passed as children
+"Hello"
}
Then, to add some typesafety, you can replace dynamic
with something like this
external interface ReactSomethingProps: RProps {
var foo: String
}
@JsModule("react-something")
external val ReactSomething: RClass<ReactSomethingProps>
How can I add children components to a component using DSL, like
fun RBuilder.app { welcome("Jane") { div() } }
This is exactly the way to add child components by now (we can change it to +div()
in future releases though)
An example about a stateful component.
You can find one in CRKA templates: https://github.com/JetBrains/create-react-kotlin-app/blob/master/packages/react-scripts/template/src/ticker/Ticker.kt
I agree that we should add such examples to kotlin-react
readme. @Leonya, @prigara, can someone of you handle it?
Thanks a lot that was really useful!
How can I add children components to a component using DSL, like
In the provided example on the frontpage, the welcome function accepts only one parameter, so I can't provide a function as a second one (the body where in my example the div were added).
Of course I can expand my function to accept an childrenBuilder: RBuilder.()->Unit
, but my question is that what should happen with this childrenBuilder
in the definition of the welcome
function? Should I apply it on this
like here?
fun RBuilder.welcome(childrenBuilder: RBuilder.()->Unit) = child(WelcomeComponent::class) {
this.childrenBuilder()
}
fun RBuilder.welcome(childrenBuilder: RBuilder.()->Unit) = child(WelcomeComponent::class) { this.childrenBuilder() }
This code assumes that you have WelcomeComponent class component declared. You can actually omit this
keyword here, which is often the case in Kotlin and Java:
fun RBuilder.welcome(childrenBuilder: RBuilder.()->Unit) =
child(WelcomeComponent::class) {
childrenBuilder()
}
Actually, you can pass not only children, but also props in that handler. So let's change its name to something more generic, and the type to In fact, it can be simplified to just There's still a question how to consume the children in your class component. Of course, you can read it from If you don't use a class component, you can do this:RHandler<WelcomeProps>. This is an alias for
RElementBuilder, where
RElementBuilderis basically
RBuilderwith an additional ability of passing props of type
Pwith
attrs.foo = "baror
attrs { foo = "bar" }`fun RBuilder.welcome(handler: RHandler<WelcomeProps>) =
child(WelcomeComponent::class) {
handler()
}
fun RBuilder.welcome(handler: RHandler<WelcomeProps>) =
child(WelcomeComponent::class, handler)
props.children
, but it doesn't have a fixed type, so it's better to use children()
helper:class Welcome: RComponent<WelcomeProps, RState>() {
override fun RBuilder.render() {
div {
+"Hello, ${props.name}"
children()
}
}
}
fun RBuilder.welcome(name: String, children: RBuilder.() -> Unit) {
div {
+"Hello, $name"
children()
}
}
Echo for add more examples, such as to cover the reactjs tutorial:) Thanks. At least to cover the todo sample:)
Here is a simple Todo list sample:
@ScottHuangZL I've edited your message to point to your PR (hope you don't mind)
@Hypnosphi NP.
Add one sample to implement react TicTacToe tutorial. Store the state in board level only in this example. https://github.com/JetBrains/kotlin-wrappers/pull/17
Will provide further example to put state at game level soon.
Complete the full function TicTacToe tutorial sample. Hope @Hypnosphi or @Leonya accept the pull request :) thanks.
Suggest both keep that 2 versions, 1st one for basic, and 2nd version for basic plus.
Hi,
I would like to ask about React Stateless Components, e.g.:
const Username = function(props) {
return (
<p>The logged in user is: {props.username}</p>
)
}
I could produce something similar with kotlin-wrappers:
override fun getComponentForKey(key: String): (props: dynamic) -> dynamic {
return { props ->
buildElements({
span {
childList.addAll(React.Children.toArray(props.children)) // ??
}
})
}
}
This method implements an interface from DraftJs, and has to return a React component.
My first question: is this implementation(using the buildElements) the right way to do this?
Second, I couldn't use the children()
method in my method at the line with comments ??
, because children
is defined inside ReactComponent
as an extension function for RBuilder
. So had to copy its implementation into that line.
Which makes me think that either my implementation is wrong, or maybe the children()
method should put into RBuilder
.
Yes, if some external library expects actual react elements, using buildElements
is the correct approach.
As for children
function, I think we could add the corresponding helper on RBuilder
, but you’d need to pass props.children
to it:
children(props.children)
@bbodi Actually, I found a way to make it work like that:
override fun getComponentForKey(key: String): (props: RProps) -> dynamic {
return { props ->
buildElements {
span {
props.children()
}
}
}
}
It works since @jetbrains/kotlin-react@16.0.0-pre.12
(included in react-scripts-kotlin@2.1.0
)
@Hypnosphi I try to import an example from https://www.npmjs.com/package/react-quill It should be show how to leverage existing external react component
Main code as below. And I try call it in app.kt as quill("<p>A quill edit <b>example</b></>")
It can normally show the given value in the quill editor, sound the import example is ok.
However, when I try change the content in the editor, it show error in the console as: The given range isn't in document.
Is it caused by the onChange need bind to this? Or I need change onChange typing as below? It seems similar error result var onChange: (String) -> Unit
Can you shed a light for how to resolve the issue.
===main code as below ======
@JsModule("react-quill")
external val reactQuill: RClass<ReactQuillProps>
external interface ReactQuillProps : RProps {
var value: String
var onChange: (Event) -> Unit
}
interface QuillProps : RProps {
var initialText: String
}
interface QuillState : RState {
var text: String
}
class Quill(props: QuillProps) : RComponent<QuillProps, QuillState>(props) {
override fun QuillState.init(props: QuillProps) {
text = props.initialText
}
private fun handleChange(value: String) {
setState {
text = value
}
console.log(value)
}
override fun RBuilder.render() {
div {
reactQuill {
attrs {
value = state.text
onChange = { handleChange(value) }
}
}
}
}
}
fun RBuilder.quill(quillValue: String) = child(Quill::class) {
attrs.initialText = quillValue
}
onChange = { handleChange(value) }
It should be { value -> handleChange(value) }
(or just { handleChange(it) }
). You can also try onChange = ::handleChange
It works! Thanks @Hypnosphi for your always help.
...
var onChange: (String) -> Unit
...
reactQuill {
attrs {
value = state.text
onChange = { handleChange(it) }
}
}
@Hypnosphi Can you add some articles/blogs to deep dive the kotlin-wrapper & kotlin-react? And then you can close this issue accordingly? Thanks.
@prigara do we have any?
@Hypnosphi, @ScottHuangZL unfortunately, we don't have any articles or tutorials yet. Maybe we should add a link to the Koltin JavaScript docs?
guys, how can i run examples from the examples folder? what gradle command should i use to start the frontend?
There are currently no setup to run the examples, you can only build them
ReactSomething {
attrs {
foo = "bar"
}
// will be passed as children
+"Hello"
}
This works perfectly. But can i still use attrs in dynamic way when import react components like this?
@JsModule("react-something")
external val ReactSomething: RClass<dynamic>
In built-in tags like div
and h1
i can use attrs["foo"]="bar"
But this doesn't work in imported react components
Right now, this should work:
attrs.asDynamic().foo = "bar"
We can find a better way to do that though. Basically, what's needed is to add an extension function operator fun RProps.set()
thanks. this one did the trick:
operator fun RProps.set(key: String, value: dynamic) {
asDynamic()[key] = value
}
@deviant-studio you can try the example from https://github.com/ScottHuangZL/my-kotlin-app I believe you have clone and successful run in your local computer. btw, please "npm install" to get necessary lib in advance after clone. Thanks.
I've been trying to create headers for the react-google-maps
library, which uses a set of higher order components withGoogleMap
and withScriptjs
to wrap a GoogleMap
component.
I write the header for GoogleMap
as follows
/**
* Headers for the react-google-maps JS library
*/
@file:JsModule("react-google-maps")
package jsheaders
import react.*
import kotlin.js.Json
external val GoogleMap : RClass<RGMapProps>
external interface RGMapProps : RProps {
var defaultZoom : Int
var defaultCenter : Json
}
as explained before.
How do I write the header for the HOCs ? I thought it would be intuitive to do
external fun withGoogleMap(component: ReactElement) : ReactElement
and defining my map component with
fun RBuilder.myMapComponent() {
withGoogleMap(GoogleMap {
attrs {
defaultZoom = 15
defaultCenter = json(
"lat" to -34.397,
"lng" to 150.644
)
}
})
}
would work.
But I still get the error
Error: Did you wrap <GoogleMap> component with withGoogleMap() HOC?
as if wrapping failed.
Now, there's an interface named HOC
in the wrapper, but I don't quite understand it. You define it using
external val withGoogleMap : HOC<RProps, RGMapProps>
right ?
Okay, I got a bit further by defining withGoogleMap
as
external val withGoogleMap : (RClass<RGMapProps>) -> RClass<RGMapProps>
and defining my map component as
fun RBuilder.myMapComponent() {
println(withGoogleMap.toString())
withGoogleMap(GoogleMap)({
attrs.defaultCenter = luxembourgCenter
attrs.defaultZoom = 15
})
}
Though I would've liked to understand how the HOC interface works.
external val withGoogleMap : HOC<RProps, RGMapProps>
yes, this one should work
How should I use it, then ? Doing
withGoogleMap(GoogleMap) { }
does not work...
What's the error?
IntelliJ warns me about an unresolved reference, which the compiler also returns :
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
What's the type of GoogleMap
in your case?
external val GoogleMap : RClass<RGMapProps>
as above.
I think it might be a problem with how I use @file:JsModule("react-google-maps")
to import the JavaScript react-google-maps
library. Somehow, it does not allow me to use the withGoogleMap
HOC object I define in my Kotlin code:
// MyMapComponent.kt
package app
import jsheaders.reactgooglemaps.*
import react.RBuilder
import kotlinext.js.*
import kotlinx.html.style
import react.dom.div
import kotlin.js.json
val augmentedGoogleMap = withGoogleMap(GoogleMap)
@file:JsModule("react-google-maps")
package jsheaders.reactgooglemaps
import react.*
import kotlin.js.Json
external val GoogleMap : RClass<RGMapProps>
external val withGoogleMap : HOC<RGMapProps, RWithGMapProps>
external val withScriptjs : HOC<RWithGMapProps, RWithScriptjsProps>
external interface RGMapProps : RProps {
var defaultZoom : Int
var defaultCenter : Json
}
external interface RWithGMapProps : RGMapProps {
var containerElement : ReactElement
var mapElement : ReactElement
}
external interface RWithScriptjsProps : RWithGMapProps {
var googleMapURL : String
var loadingElement : ReactElement
}
Weird, for me it compiles without an error. Which versions on Kotlin and kotlin-react
do you use?
I am using Kotlin 1.2.31 and kotlin-react:16.3.1-pre.25-kotlin-1.2.30
It seems importing react.invoke
does the trick, for some reason...
import react.*
should work as well
Yes it does! I'm thinking of providing my wrapper for react-google-maps
as an example. I'll get around to making a repo for it soon enough
Are there any examples of how to import methods defined in React components ?
Which components expect you to do that? They really shouldn’t
The GoogleMap
component of react-google-maps
has a method called getBounds
defined. I was wondering about how to expose it to Kotlin.
Is its usage (in JS) documented somewhere?
Yes, here https://tomchentw.github.io/react-google-maps/#googlemap It's not very detailed, though
OK, you should be able to do something like this:
external interface GoogleMapInstance {
fun getBounds(): Bounds
}
class MyMapComponent: RComponent<RProps, RState>() {
var mapRef: GoogleMapInstance? = null
fun getBounds(): Bounds? = mapRef?.getBounds()
override fun RBuilder.render() {
withGoogleMap(GoogleMap)({
ref { mapRef = it }
})
}
}
Or, using React 16.3 and kotlin-react 16.3.1-pre.27:
external interface GoogleMapInstance {
fun getBounds(): Bounds
}
class MyMapComponent: RComponent<RProps, RState>() {
var mapRef = createRef<GoogleMapInstance>()
fun getBounds(): Bounds? = mapRef.current?.getBounds()
override fun RBuilder.render() {
withGoogleMap(GoogleMap)({
ref = mapRef
})
}
}
Thanks, I wrote my AugmentedComponent
class as in this gist ; the code compiles and the map displays. I'll try it out to see if the imported methods work.
Just so I understand, what you're doing with
withGoogleMap(GoogleMap)({
ref = mapRef
})
is passing a lambda to the composed GoogleMap component, with RWithGoogleMap props ?
Here I'm using new createRef feature: https://reactjs.org/blog/2018/03/29/react-v-16-3.html#createref-api
Can you provide please more examples. E.g.
In the kotlin-fullstack-example there is an
open class ReactExternalComponentSpec<P : RProps>(val ref: dynamic)
class for imporing React components from external modules. How can I achieve the same with kotlin-react?How can I add children components to a component using DSL, like
An example about a stateful component.
Thanks!