State Management¶
State management is a critical element of building interactive apps because it allows you store information about what the user did in a structured way.
Basic usage¶
You can register a class using the class decorator me.stateclass
which is like a dataclass with special powers:
You can get an instance of the state class inside any of your Mesop component functions by using me.state
:
Use immutable default values¶
Similar to regular dataclasses which disallow mutable default values, you need to avoid mutable default values such as list and dict for state classes. Using mutable default values can result in leaking state across sessions which can be a serious privacy issue.
You MUST use immutable default values or use dataclasses field
initializer or not set a default value.
Good: immutable default value
Setting a default value to an immutable type like str is OK.
Bad: mutable default value
The following will raise an exception because dataclasses prevents you from using mutable collection types like list
as the default value because this is a common footgun.
If you set a default value to an instance of a custom type, an exception will not be raised, but you will be dangerously sharing the same mutable instance across sessions which could cause a serious privacy issue.
Good: default factory
If you want to set a field to a mutable default value, use default_factory in the field
function from the dataclasses module to create a new instance of the mutable default value for each instance of the state class.
Good: no default value
If you want a default of an empty list, you can just not define a default value and Mesop will automatically define an empty list default value.
For example, if you write the following:
It's the equivalent of:
How State Works¶
me.stateclass
is a class decorator which tells Mesop that this class can be retrieved using the me.state
method, which will return the state instance for the current user session.
If you are familiar with the dependency injection pattern, Mesop's stateclass and state API is essentially a minimalist dependency injection system which scopes the state object to the lifetime of a user session.
Under the hood, Mesop is sending the state back and forth between the server and browser client so everything in a state class must be serializable.
Multiple state classes¶
You can use multiple classes to store state for the current user session.
Using different state classes for different pages or components can help make your app easier to maintain and more modular.
@me.stateclass
class PageAState:
...
@me.stateclass
class PageBState:
...
@me.page(path="/a")
def page_a():
state = me.state(PageAState)
...
@me.page(path="/b")
def page_b():
state = me.state(PageBState)
...
Under the hood, Mesop is managing state classes based on the identity (e.g. module name and class name) of the state class, which means that you could have two state classes named "State", but if they are in different modules, then they will be treated as separate state, which is what you would expect.
Nested State¶
You can also have classes inside of a state class as long as everything is serializable:
class NestedState:
val: str
@me.stateclass
class State:
nested: NestedState
def app():
state = me.state(State)
Note: you only need to decorate the top-level state class with
@me.stateclass
. All the nested state classes will automatically be wrapped.
Nested State and dataclass¶
Sometimes, you may want to explicitly decorate the nested state class with dataclass
because in the previous example, you couldn't directly instantiate NestedState
.
If you wanted to use NestedState as a general dataclass, you can do the following:
@dataclass
class NestedState:
val: str = ""
@me.stateclass
class State:
nested: NestedState
def app():
state = me.state(State)
Reminder: because dataclasses do not have default values, you will need to explicitly set default values, otherwise Mesop will not be able to instantiate an empty version of the class.
Now, if you have an event handler function, you can do the following:
def on_click(e):
response = call_api()
state = me.state(State)
state.nested = NestedState(val=response.text)
If you didn't explicitly annotate NestedState as a dataclass, then you would get an error instantiating NestedState because there's no initializer defined.
Tips¶
State performance issues¶
Take a look at the performance guide to learn how to identify and fix State-related performance issues.
Next steps¶
Event handlers complement state management by providing a way to update your state in response to user interactions.