reactjskotlinkotlin-coroutineskotlin-js

How do I call a suspend function from a React button onClick event in Kotlin/JS?


I want to fetch some data when a button is pressed. I know how to do it using promises and continuations (see the sample below) but I'd like to learn how to do it using suspend functions.

As an example:

import kotlinx.browser.window
import react.FC
import react.Props
import react.dom.html.ReactHTML.button
import react.dom.html.ReactHTML.div
import react.useState
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

val MyComponent = FC<Props> {
    var text by useState("Click Load")
    div { +text }
    button {
        +"Load"
        onClick = {
            text = "Loading..."
            window.fetch("https://catfact.ninja/fact").then { response ->
                response.text().then { receivedText ->
                    text = receivedText
                }
            }
            
            //text = fetchCat() //This line does not compile
        }
    }
}

suspend fun fetchCat() = suspendCoroutine<String> { continuation ->
    window.fetch("https://catfact.ninja/fact")
        .then { response ->
            response.text()
                .then { text -> continuation.resume(text) }
                .catch { continuation.resumeWithException(it) }
        }
        .catch { continuation.resumeWithException(it) }
}

Solution

  • A reference to kotlinx.coroutines needs to be added to gradle:

    dependencies {
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
    }
    

    Then a MainScope is created globally (a single instance is used for the whole application), using which suspend functions can be called.

    import kotlinx.browser.window
    import kotlinx.coroutines.MainScope
    import kotlinx.coroutines.launch
    import react.FC
    import react.Props
    import react.dom.html.ReactHTML.button
    import react.dom.html.ReactHTML.div
    import react.useState
    import kotlin.coroutines.resume
    import kotlin.coroutines.resumeWithException
    import kotlin.coroutines.suspendCoroutine
    
    val mainScope = MainScope()
    
    val MyComponent = FC<Props> {
        var text by useState("Click Load")
        div { +text }
        button {
            +"Load"
            onClick = {
                text = "Loading..."
    
                mainScope.launch {
                    text = fetchCat()
                }
            }
        }
    }
    
    suspend fun fetchCat() = suspendCoroutine { continuation ->
        window.fetch("https://catfact.ninja/fact")
            .then { response ->
                response.text()
                    .then { text -> continuation.resume(text) }
                    .catch { continuation.resumeWithException(it) }
            }
            .catch { continuation.resumeWithException(it) }
    }