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 committed 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 occurring 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(onIncrementCounter = { 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!",
)
}
}

Batching Updates​

Litho has an internal mechanism where it batches state updates to avoid the scenario of doing one layout re-calculation per state update (and corresponding mount) and, therefore become more performant.

Internally, Litho achieves this by leveraging the lifecycle of the Choreographer API and how it breaks down its different work phases. Whenever the Choreographer receives a VSYNC signal, it starts a cycle to prepare the next Frame. In this cycle, it goes through 3 main phases:

  1. Input handling
  2. Animation (which layouts, measures, and draws)
  3. Traversals

When Litho enqueues a new state update, it will schedule its layout calculation on the Choreographer's next animation phase. This way, any subsequent state update enqueued before the next animation phase will be taken into account in the following layout calculation (and not re-schedule any more work).

Batched Updates Process