🚀 Gen 5 of the leading AI app deployment platform launches October 6. Click for the livestream.

Reactive variables

Inspired by Shiny I decided to try to implement the concept of reactive variables in Dash. It’s basically a way to simplify sharing state between callbacks via a variable that reacts to input. Here is a small example where a reactive variable stores a (dynamically) filtered dataframe, which is then used in multiple callbacks,

import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
import dash_table
from dash_extensions.enrich import DashProxy, Output, Input, ServersideOutputTransform

# Read the full, complex dataset here (for the sake of simplicify, a small px dataset is used).
df_all = px.data.gapminder()
# Example app demonstrating how to share state between callbacks via a reactive variable.
app = DashProxy(transforms=[ServersideOutputTransform()])
app.layout = html.Div([
    dcc.Dropdown(options=[dict(value=x, label=x) for x in df_all.country.unique()], id="country", value="Denmark"),
    dcc.Graph(id='graph'),
    dash_table.DataTable(id='table', columns=[{"name": i, "id": i} for i in df_all.columns])
])

@app.reactive(Input('country'))  # default prop for input/state is "value"
def df_filtered(country):  # reactive variable name = function name
    return df_all[df_all.country == country]  # defaults to serverside output, i.e. json serialization is not needed

@app.callback(Output('table', 'data'), Input('df_filtered'))  # access reactive variable via its name
def update_table(df):
    return df.to_dict('records')  # the reactive variable was stored serverside, i.e. deserialize is not needed

@app.callback(Output('graph', 'figure'), Input('df_filtered'))
def update_graph(df):
    return px.bar(df, x='year', y='pop', color='gdpPercap')

if __name__ == "__main__":
    app.run_server()

If you want to play with it, you can try it out with dash-extensions==0.0.59rc1 :slight_smile:

Animation

5 Likes

Awesome @Emil ! Could you describe a bit how this works “under the hood”? Where the data is stored, how/if it works across processes/containers, and how it triggers the next callback?

Thanks, @chriddyp! The reactive variables are built on top of the existing callback system with the data stored in Store elements that are added to the layout automatically. If the ServersideOutputTransform is not loaded (or the serverside flag is false) the data will reside clientside, otherwise serverside (recommended). Like normal callbacks, it “just works” in the clientside case, while the serverside case requires the ServersideOutputTransform to be configured with an appropriate backend such as Redis to work across processes/containers.

Very nice solution.

Does the ServersideOutputTransform use a dcc.Store to “trigger” the callback while storing the results in Redis? Sort of like the “Caching & Signalling” method.

Have you thought about how clearing the data from Redis memory might happen? In the past, I’ve used things like Redis’s time expiring keys.

Yes, it is the change in the data property of the underlying Store that triggers the following callback.

If an app tries to access data that has been cleared from the underlying store (whether it is file base, redis based, or else), the app breaks. To avoid that it happens, the default file store has infinite timeout. Instead, I typically clear the files outside business hours (e.g. at 3 am). For the redis case, I generally recommend people to use a very high timeout, say 24 hours, to be safe. But I agree that this is the probably the “least polished point” of the current solution; please let me know if you have ideas for improvements :slight_smile:

2 Likes

This is great. Would you say that app.reactive is just an API alternative to ServersideOutput?

Or are there other features that app.reactive provides?

I would rather than say the app.reactive is an API alternative to app.callback offering syntactical sugar for sharing state between callbacks. It can’t do anything you couldn’t do with a normal callback, but it abstracts away the underlying Store. Compared to the app.callback it does a few things to achieve ,

  • The Store component is created automatically
  • The Output or ServersideOutput object is created automatically
  • The component property specification (i.e. data) is automated, i.e. you don’t need specify it
  • The component id specification is derived from the function name (though I am a little in doubt if this is the best way to do it, and it is not strictly necessary for the reactive variable concept)
2 Likes

This is really cool! Storing and sharing intermediate values is such a common use case, it’s great to see it streamlined.

Do you think it could be a part of this project?: 📣 Modular & Reusable Python Components (With Attached Callbacks)

1 Like

Yes indeed! I could imagine component authors exposing certain reactive / server side properties for intermediate values.

We would need to make sure that it’s possible to declare a reactive variable / server side output without necessarily having it necessarily used as an input. There is a performance / resource cost of declaring these (sending data in stores or storing in redis memory), so it would be nice if the framework could somehow detect if the declared reactive output was actually used before saving it in memory.