edifice.use_state#

edifice.use_state(initial_state)[source]#

Persistent mutable state Hook inside a @component function.

Behaves like React useState.

Parameters:

initial_state (Union[TypeVar(_T_use_state), Callable[[], TypeVar(_T_use_state)]]) – The initial state value or initializer function.

Return type:

tuple[TypeVar(_T_use_state), Callable[[Union[TypeVar(_T_use_state), Callable[[TypeVar(_T_use_state)], TypeVar(_T_use_state)]]], None]]

Returns:

A tuple pair containing

  1. The current state value.

  2. A setter function for setting or updating the state value.

use_state() is called with an initial value. It returns a state value and a setter function.

The state value will be the value of the state at the beginning of the render for this component.

The setter function will, when called, set the state value before the beginning of the next render. If the new state value is not __eq__ to the old state value, then the component will be re-rendered.

@component
def Stateful(self):
    x, x_setter = use_state(0)

    Button(
        title=str(x)
        on_click = lambda _event: x_setter(x + 1)
    )

The setter function is referentially stable, so it will always be __eq__ to the setter function from the previous render. It can be passed as a prop to child components.

The setter function should be called inside of an event handler or a use_effect() function.

Never call the setter function directly during a @component render function.

Warning

The state value must not be a Callable, so that Edifice does not mistake it for an initializer function or an updater function.

If you want to store a Callable value, like a function, then wrap it in a tuple or some other non-Callable data structure.

Initialization#

The initial value can be either a state value or an initializer function.

An initializer function is a function of no arguments.

If an initializer function is passed to use_state() instead of an state value, then the initializer function will be called once before this components()’s first render to get the initial state.

Initializer function#
def initializer() -> tuple[int]:
    return tuple(range(1000000))

intlist, intlist_set = use_state(initializer)

This is useful for one-time construction of initial state if the initial state is expensive to compute.

If an initializer function raises an exception then Edifice will crash.

Do not perform observable side effects inside the initializer function.

  • Do not write to files or network.

  • Do not call a setter function of another use_state() Hook.

For these kinds of initialization side effects, use use_effect() instead, or use_async() for very long-running initial side effects.

Using the initializer function for initial side effects is good for some cases where the side effect has a predictable result and cannot fail, like for example setting global styles in the root Element, or reading small configuration files.

Update#

All updates from calling the setter function will be applied before the beginning of the next render.

A setter function can be called with a state value or an updater function.

An updater function is a function from the previous state value to the new state value.

If an updater function is passed to the setter function, then before the beginning of the next render the state value will be modified by calling all of the updater functions in the order in which they were set.

Updater function#
@component
def Stateful(self):
    x, x_setter = use_state(0)

    def updater(x_previous:int) -> int:
        return x_previous + 1

    Button(
        title=str(x)
        on_click = lambda _event: x_setter(updater)
    )

If any of the updater functions raises an exception then Edifice will crash.

State must not be mutated#

Do not mutate the state variable. The old state variable must be left unmodified so that it can be compared to the new state variable during the next render.

If Python does not have an immutable version of your state data structure, like for example the dict, then you just have to take care to never mutate it.

Instead of mutating a state list, create a shallow copy of the list, modify the copy, then call the setter function with the modified copy.

Updater function with shallow copy of a list#
from copy import copy
from typing import cast

def Stateful(self):
    x, x_setter = use_state(cast(list[str], []))

    def updater(x_previous:list[str]) -> list[str]:
        x_new = copy(x_previous)
        x_new.append("another")
        return x_new

    with View():
        Button(
            title="Add One",
            on_click = lambda _event: x_setter(updater)
        )
        for t in x:
            Label(text=t)

Techniques for immutable datastructures in Python#

  • Shallow copy. We never need a deep copy because all the data structure items are also immutable.

  • Frozen dataclasses. Use the replace() function to update the dataclass.

  • Tuples (my_list:tuple[str, ...]) instead of lists (my_list:list[str]).

Updater function with shallow copy of a tuple#
from typing import cast

def Stateful(self):
    x, x_setter = use_state(cast(tuple[str, ...], ()))

    def updater(x_previous:tuple[str, ...]) -> tuple[str, ...]:
        return x_previous + ("another",)

    with View():
        Button(
            title="Add One",
            on_click = lambda _event: x_setter(updater)
        )
        for t in x:
            Label(text=t)