Skip to main content

useState

useState is a hook that allows a component to persist and update a single value across renders and is the most common hook you'll encounter.

To familiarize yourself with the concept and rules for hooks, see the Introduction to Hooks page.

Declaring and Using State​

Declare state with useState, passing an initial value. useState will always return a State<T> holding whatever the current value of that state is. On the first render, this is the initial value, however in subsequent renders it will include any updates that have been commited for that state variable.

The following code shows an example of a component that renders a counter that increments when it is clicked. useState is used to track the value of that counter over time:

class CounterComponent : KComponent() {
override fun ComponentScope.render(): Component {
val clicks = useState { 0 }
return Text(
style = Style.onClick { clicks.update { c -> c + 1 } },
text = "Clicks: ${clicks.value}")
}
}

Updating State​

note

update causes the re-render to be async by default. If you need to perform a synchronous state update (that is, one which will cause a re-render synchronously on the current thread), use updateSync.

To update state, use the update method on the State<T> returned from useState: this will trigger a render pass with the new state value:

class CheckboxComponent : KComponent() {
override fun ComponentScope.render(): Component? {
val isChecked = useState { false }

return Column(style = Style.onClick { isChecked.update { currValue -> !currValue } }) {
child(
Image(
drawable =
drawableRes(
if (isChecked.value) {
android.R.drawable.checkbox_on_background
} else {
android.R.drawable.checkbox_off_background
})))
}
}
}

Value vs. Lambda variants​

update and updateSync have two variants:

  1. Value variant - takes a determined value, such as myState.update(1).
  2. Lambda variant - takes a lambda receiving the old value which can be used to compute a new value, such as myState.update { c -> c + 1 }.

The basic rule of thumb is: use the lambda variant when you need to perform an update that depends on the old state value. This is because value on State is a snapshot of that state for the current render and may not reflect any renders that have occurred since (or are currently occuring on other threads).

For example, if your state update increments a counter, then using the function version of update with count -> count + 1 enables you to account for updates that are in flight but not yet applied (such as if the user has tapped a button triggering the update multiple times in succession).

Using state in child components​

Avoid passing a State directly to the child component. Instead, pass a lambda to the child which it can invoke to notify the parent to update the state:

class StateParentChildComponent : KComponent() {
override fun ComponentScope.render(): Component {
val clicks = useState { 0 }
return Column {
child(ChildComponent { clicks.update { c -> c + 1 } })
child(Text(text = "Counter: ${clicks.value}"))
}
}
}

And then in the child component:

class ChildComponent(private val onIncrementCounter: () -> Unit) : KComponent() {
override fun ComponentScope.render(): Component {
return Text(
style = Style.onClick { onIncrementCounter() },
text = "Tap to increment the parent!",
)
}
}