JetBrains / kotlin-wrappers

Kotlin wrappers for popular JavaScript libraries
Apache License 2.0
1.36k stars 161 forks source link

More examples #13

Closed bbodi closed 3 years ago

bbodi commented 7 years ago

Can you provide please more examples. E.g.

Thanks!

Hypnosphi commented 7 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?

bbodi commented 7 years ago

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()
}
Hypnosphi commented 7 years ago
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 RHandler<WelcomeProps>. This is an alias forRElementBuilder.() -> Unit, whereRElementBuilder

is basicallyRBuilderwith an additional ability of passing props of typePwithattrs.foo = "barorattrs { foo = "bar" }`

fun RBuilder.welcome(handler: RHandler<WelcomeProps>) =
    child(WelcomeComponent::class) {
        handler()
    }

In fact, it can be simplified to just

fun RBuilder.welcome(handler: RHandler<WelcomeProps>) =
    child(WelcomeComponent::class, handler)

There's still a question how to consume the children in your class component. Of course, you can read it from 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()
        }
    }
}

If you don't use a class component, you can do this:

fun RBuilder.welcome(name: String, children: RBuilder.() -> Unit) {
    div {
        +"Hello, $name"
        children()
    }
}
ScottHuangZL commented 6 years ago

Echo for add more examples, such as to cover the reactjs tutorial:) Thanks. At least to cover the todo sample:)

ScottHuangZL commented 6 years ago

Here is a simple Todo list sample:

https://github.com/JetBrains/kotlin-wrappers/pull/16

Hypnosphi commented 6 years ago

@ScottHuangZL I've edited your message to point to your PR (hope you don't mind)

ScottHuangZL commented 6 years ago

@Hypnosphi NP.

ScottHuangZL commented 6 years ago

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.

ScottHuangZL commented 6 years ago

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.

https://github.com/JetBrains/kotlin-wrappers/pull/18

ScottHuangZL commented 6 years ago

https://github.com/ScottHuangZL/my-kotlin-app

ScottHuangZL commented 6 years ago

https://github.com/JetBrains/kotlin-wrappers/pull/21

This is a port of https://reactjs.org/docs/thinking-in-react.html

bbodi commented 6 years ago

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.

Hypnosphi commented 6 years ago

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)
Hypnosphi commented 6 years ago

@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)

ScottHuangZL commented 6 years ago

@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
}
Hypnosphi commented 6 years ago

onChange = { handleChange(value) }

It should be { value -> handleChange(value) } (or just { handleChange(it) }). You can also try onChange = ::handleChange

ScottHuangZL commented 6 years ago

It works! Thanks @Hypnosphi for your always help.

... 
var onChange: (String) -> Unit
...
reactQuill {
                attrs {
                    value = state.text
                    onChange = { handleChange(it) }
                }
            }
ScottHuangZL commented 6 years ago

@Hypnosphi Can you add some articles/blogs to deep dive the kotlin-wrapper & kotlin-react? And then you can close this issue accordingly? Thanks.

Hypnosphi commented 6 years ago

@prigara do we have any?

prigara commented 6 years ago

@Hypnosphi, @ScottHuangZL unfortunately, we don't have any articles or tutorials yet. Maybe we should add a link to the Koltin JavaScript docs?

mykola-dev commented 6 years ago

guys, how can i run examples from the examples folder? what gradle command should i use to start the frontend?

Hypnosphi commented 6 years ago

There are currently no setup to run the examples, you can only build them

mykola-dev commented 6 years ago
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

Hypnosphi commented 6 years ago

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()

mykola-dev commented 6 years ago

thanks. this one did the trick:

operator fun RProps.set(key: String, value: dynamic) {
    asDynamic()[key] = value
}
ScottHuangZL commented 6 years ago

@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.

ManifoldFR commented 6 years ago

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 ?

ManifoldFR commented 6 years ago

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.

Hypnosphi commented 6 years ago
external val withGoogleMap : HOC<RProps, RGMapProps>

yes, this one should work

ManifoldFR commented 6 years ago

How should I use it, then ? Doing

withGoogleMap(GoogleMap) { }

does not work...

Hypnosphi commented 6 years ago

What's the error?

ManifoldFR commented 6 years ago

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:

Hypnosphi commented 6 years ago

What's the type of GoogleMap in your case?

ManifoldFR commented 6 years ago
external val GoogleMap : RClass<RGMapProps>

as above.

ManifoldFR commented 6 years ago

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
}
Hypnosphi commented 6 years ago

Weird, for me it compiles without an error. Which versions on Kotlin and kotlin-react do you use?

ManifoldFR commented 6 years ago

I am using Kotlin 1.2.31 and kotlin-react:16.3.1-pre.25-kotlin-1.2.30

ManifoldFR commented 6 years ago

It seems importing react.invoke does the trick, for some reason...

Hypnosphi commented 6 years ago

import react.* should work as well

ManifoldFR commented 6 years ago

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

ManifoldFR commented 6 years ago

Are there any examples of how to import methods defined in React components ?

Hypnosphi commented 6 years ago

Which components expect you to do that? They really shouldn’t

ManifoldFR commented 6 years ago

The GoogleMap component of react-google-maps has a method called getBounds defined. I was wondering about how to expose it to Kotlin.

Hypnosphi commented 6 years ago

Is its usage (in JS) documented somewhere?

ManifoldFR commented 6 years ago

Yes, here https://tomchentw.github.io/react-google-maps/#googlemap It's not very detailed, though

Hypnosphi commented 6 years ago

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
     })
  }
}
ManifoldFR commented 6 years ago

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 ?

Hypnosphi commented 6 years ago

Here I'm using new createRef feature: https://reactjs.org/blog/2018/03/29/react-v-16-3.html#createref-api