Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More examples #13

Closed
bbodi opened this issue Nov 14, 2017 · 48 comments
Closed

More examples #13

bbodi opened this issue Nov 14, 2017 · 48 comments

Comments

@bbodi
Copy link

bbodi commented Nov 14, 2017

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

fun RBuilder.app {
    welcome("Jane") {
        div()
     }
}
  • An example about a stateful component.

Thanks!

@Hypnosphi
Copy link
Contributor

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
Copy link
Author

bbodi commented Nov 15, 2017

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
Copy link
Contributor

Hypnosphi commented Nov 15, 2017

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 for RElementBuilder.() -> Unit, where RElementBuilder

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
Copy link
Contributor

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

@ScottHuangZL
Copy link
Contributor

ScottHuangZL commented Nov 20, 2017

Here is a simple Todo list sample:

#16

@Hypnosphi
Copy link
Contributor

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

@ScottHuangZL
Copy link
Contributor

@Hypnosphi NP.

@ScottHuangZL
Copy link
Contributor

Add one sample to implement react TicTacToe tutorial. Store the state in board level only in this example.
#17

Will provide further example to put state at game level soon.

@ScottHuangZL
Copy link
Contributor

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.

#18

@ScottHuangZL
Copy link
Contributor

@ScottHuangZL
Copy link
Contributor

#21

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

@bbodi
Copy link
Author

bbodi commented Nov 26, 2017

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
Copy link
Contributor

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
Copy link
Contributor

@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/[email protected] (included in [email protected])

@ScottHuangZL
Copy link
Contributor

@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
Copy link
Contributor

onChange = { handleChange(value) }

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

@ScottHuangZL
Copy link
Contributor

ScottHuangZL commented Nov 30, 2017

It works! Thanks @Hypnosphi for your always help.

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

@ScottHuangZL
Copy link
Contributor

@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
Copy link
Contributor

@prigara do we have any?

@prigara
Copy link
Contributor

prigara commented Dec 4, 2017

@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
Copy link

mykola-dev commented Feb 18, 2018

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

@Hypnosphi
Copy link
Contributor

Hypnosphi commented Feb 18, 2018

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

@mykola-dev
Copy link

mykola-dev commented Feb 25, 2018

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
Copy link
Contributor

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
Copy link

thanks. this one did the trick:

operator fun RProps.set(key: String, value: dynamic) {
    asDynamic()[key] = value
}

@ScottHuangZL
Copy link
Contributor

@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
Copy link

ManifoldFR commented Apr 8, 2018

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
Copy link

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
Copy link
Contributor

external val withGoogleMap : HOC<RProps, RGMapProps>

yes, this one should work

@ManifoldFR
Copy link

How should I use it, then ? Doing

withGoogleMap(GoogleMap) { }

does not work...

@Hypnosphi
Copy link
Contributor

What's the error?

@ManifoldFR
Copy link

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:

  • public final operator fun <P: RProps> RClass.invoke(handler:RHandler /* = RElementBuiler<???>.() -> Unit*/): ReactElement defined in react.RBuilder

@Hypnosphi
Copy link
Contributor

What's the type of GoogleMap in your case?

@ManifoldFR
Copy link

external val GoogleMap : RClass<RGMapProps>

as above.

@ManifoldFR
Copy link

ManifoldFR commented Apr 9, 2018

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
Copy link
Contributor

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

@ManifoldFR
Copy link

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

@ManifoldFR
Copy link

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

@Hypnosphi
Copy link
Contributor

import react.* should work as well

@ManifoldFR
Copy link

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
Copy link

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

@Hypnosphi
Copy link
Contributor

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

@ManifoldFR
Copy link

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

@Hypnosphi
Copy link
Contributor

Is its usage (in JS) documented somewhere?

@ManifoldFR
Copy link

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

@Hypnosphi
Copy link
Contributor

Hypnosphi commented Apr 17, 2018

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
Copy link

ManifoldFR commented Apr 17, 2018

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
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants