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
:
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
Set mutable default values (e.g. list) correctly
Similar to regular dataclasses which disallow mutable default values, you need to avoid mutable default values such as list and dict for state classes. Allowing mutable default values could lead to erroneously sharing state across users which would be bad!
Bad: Setting a mutable field directly on a state class attribute.
Good: Use dataclasses field
method to define a default factory so a new instance of the mutable value is created with each state class instance.
from dataclasses import field
@me.stateclass
class State:
x: list[str] = field(default_factory=lambda: ["a"])
State performance issues
Because the state class is serialized and sent back and forth between the client and server, you should try to keep the state class reasonably sized. For example, if you store a very large string (e.g. base64-encoded image) in state, then it will degrade performance of your Mesop app. Instead, you should try to store large data outside of the state class (e.g. in-memory, filesystem, database, external service) and retrieve the data as needed for rendering.