DEV Community

mavines
mavines

Posted on

Kotlin React Hooks with Coroutines

I've been trying my hand at full-stack Kotlin with React and ran into a sticky situation with useState and coroutines. For those with more knowledge of React, this may seem obvious, but it took me a while to work through.

My initial component was attempting to update state from within a coroutine loop:

import react.*
import kotlinx.coroutines.*
import react.FC
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.h1

private val scope = MainScope()

val counterComponent = FC<Props> {
    var counter by useState(0)

    useEffectOnce {
        scope.launch {
            while (true) {
                counter++
                delay(1000)
            }
        }
    }

    div {
        h1 { +"Count: $counter" }
    }
}
Enter fullscreen mode Exit fullscreen mode

The result is a component that displays '0' for a second and then continues to display '1' and never increases. After many println, useCallback, and Google searches I found this from the React FAQs. The behavior of the alert is the same that I was seeing in my coroutine. The counter was being incremented, but it was always being incremented from '0' to '1'. After reading up on useRef and poking around the React FAQ more, I found the solution.

import react.*
import kotlinx.coroutines.*
import react.FC
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.h1

private val scope = MainScope()

val counterComponent= FC<Props> {
    val (_, forceUpdate) = useReducer<Int, Int>({ x, _ -> x + 1 }, 0)
    val counter = useRef(0)
    val update = useCallback {
        counter.current = (counter.current ?: 0) + 1
        forceUpdate(0)
    }

    useEffectOnce {
        scope.launch {
            while (true) {
                update()
                delay(1000)
            }
        }
    }

    div {
        h1 { +"Count: ${counter.current}" }
    }
}
Enter fullscreen mode Exit fullscreen mode

Kotlin has some additional functions in useRefState, useRefCallback, and useRefValue which may be helpful, but I haven't figured out how to use them yet.

Discussion (0)