Keys and Component Identity
Keys help Litho to set a unique identity on a component that represents a node in the component tree. Litho uses keys to keep track of component identity between layout changes and correctly identify a component as the target of a state update.
Based on its type and the key of its parent, Litho automatically sets a key on each component. However, there are situations when there may be a need to add, remove or rearrange components in the UI, or conditionally add certain components.
This page explains how keys are autogenerated by Litho and why sometimes it's necessary to provide manual keys when they can't be autogenerated.
As long as a component is rendered in the same node in the component tree (see the images, below), it will be assigned the same key. If that node changes position (for example, it's moved to a different parent or it changes position because other sibling nodes are removed or inserted), its key is not guaranteed to be same between UI updates.
This is an important consideration because Litho uses the key to determine which component to update when calling a state update and to correctly identify this component when traversing the tree and setting the new state value.
Automatically assigned keysβ
Litho generates component keys based on their type and position relative to the parent, as shown in the folowing Component Tree.
The key of a component is a concatenation of the following:
- Parent's key - when the component is a child.
- Component's key - determined by its type.
- Deduplication ID - the position of this component between the other sibling components of the same type.
To reduce the chance of accidental key collision, there are other separators that are included in the key calculation but, for simplification, they are not included in this example.
The following diagram shows the same Component Tree with added keys.
Whenever a key collision is detected in a ComponentTree, which can happen when a parent component created multiple children components of the same type, Litho assigns a unique key to those siblings depending on their order. This means that the keys that are autogenerated are not stable if a component moves its position.
The following diagram shows the Component Tree with the first Row Component removed.
After the update, the second Row component in the initial tree is now the first child of type Row, so its key will change!
Litho was mapping this Row's state to its initial key, so all its state values will be reset after the update. Of greater consequence is that the state for that key will be associated with the next Row component, which is being assigned that key! You can imagine how this can lead to undesirable UI bugs.
Litho key autogeneration is best-effort, but cannot be fully determinist with a runtime implementation.
Assigning manual keysβ
For dynamic UI hierarchies where components can change position, manual keys that are stable between UI updates must be assigned to components. The manual key will always take precedence over the autogenerated one.
- Kotlin API
- Spec API
return Column(
style =
Style.onVisible {
if (!logOnce.value) {
// do some logging
logOnce.value = true
}
}) { // end_use_ref
if (isFirstCounterEnabled.value) {
child(
key("first_row") {
Row {
child(CounterComponent())
child(
Text(
text = "X",
textSize = 30.dp,
style =
Style.margin(all = 30.dp).onClick {
isFirstCounterEnabled.update(false)
}))
}
})
}
Row.create(c)
.key("first_row")
.child(CounterComponent.create(c))
.child(
Text.create(c)
.text("X")
.paddingPx(YogaEdge.START, 16)
.clickHandler(IdentityRootComponent.onClickRemoveFirstChild(c)))
Setting manual keys is also a nifty way to force a component's state value to be initialised again based on the value of certain props for example, if the manual key is a function of those props.