Skip to main content

Introduction to Hooks

note

Definition: Hooks are special functions that can persist state across renders and perform side-effects1 . They begin with use and may only be called from render() and other hooks. The API is heavily inspired by React Hooks.

Hooks are Litho's way to enable components to persist state across renders and register for lifecycle events while remaining immutable. Litho has a suite of built in hooks whose docs you'll find in this section.

As a brief introduction, some of the most common ones you'll encounter are:

  • useState - keeps track of a variable across renders.
  • useEffect - lets a component perform side-effects when it's attached or detached from the tree.
  • useRef - keeps track of a mutable reference across renders.
  • useCached - keeps track of a cached value that is expensive to calculate.

Hooks in Practice​

To demonstrate the use of hooks in practice, the component below implements a simple counter; whenever it's clicked, it increments the number of clicks and re-renders:

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}")
}
}

As shown in the above code, useState is a hook that can keep track of a variable across renders. The first time it's called it returns the initial value (0). Subsequent calls return the current value of the state. For more information about state, see the useState page.

The identity of useState is just the sequence of the call within the render function. So, to add another state variable, you can just add another call to useState. However, this leads to some important rules about preserving the identity of hooks!

Rules for Hooks​

Hooks maintain state by using the identity of the current component along with the sequence of the call within the component. This means that in a render, the first useState call will maintain one piece of state, a second useState call will maintain another, and so on, and they'll be consistent across render passes.

As such, there are three hooks rules that you need to remember:

  1. Hooks can only be called from render() or from another hook.

  2. Hooks must not be called conditionally.

    if (something) {
    val state = useState { 1 } // INCORRECT. Called inside an if block
    }
    val cachedValue = useCached(dependendency) {
    val dynamicColor = useBinding {0} // INCORRECT. The useCached block will only be executed if the dependency changes.
    return false
    }
  3. Hook names should start with use.

The reasons for rules 1 and 2 are to preserve hook identity, as mentioned above. Rule 3 is just to make it easier to tell when a function has hook-like behavior.

It may prove helpful to look at some more examples, starting with the most fundamental hook: useState!


  1. Side-effects are operations that affect your component. Side-effects include operations such as fetch requests, subscriptions, using timers, and manually changing the DOM. Side-effects cannot occur during rendering. For more information on side-effects, see the useEffect hook.↩