Persistence in DataTable in Multi-Page Apps

Hi,

I have an app where user in one page, say data.py, adds some data, and later some of the rows can be viewed and removed on another page, grid.py. The user should be able to later get back to data.py and add some more data.

The problem: data is not persisted between the visits to the grid.py. How can I achieved that? I tried setting persistence property, but that didn’t get me anywhere. Any tips will be most welcomed!

When I use a single-page app, similar code works.

Here’s my minimal reproducible example:

app.py

from dash import html, dcc
import dash

app = dash.Dash(__name__, use_pages=True)

app.layout = html.Div(
    [
        dcc.Store(id="store", data={}),
        html.H1("Multi Page App Demo: Sharing data between pages"),
        html.Div(
            [
                html.Div(
                    dcc.Link(f"{page['name']}", href=page["path"]),
                )
                for page in dash.page_registry.values()
            ]
        ),
        html.Hr(),
        dash.page_container,
    ]
)


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

data.py

from dash import html, Input, Output, callback, register_page
from dash.exceptions import PreventUpdate
import random

register_page(__name__, path="/")


layout = html.Div(
    [
        html.H3("Data input"),
        html.Button("Add row", id="button_id"),
        html.Br(),
        html.Div(id="my-output"),
    ]
)


@callback(
    [Output("store", "data"), Output("my-output", "children")],
    Input("button_id", "n_clicks"),
    prevent_initial_call=True
)
def add_data(n_clicks):
    if n_clicks:
        new_data = [{"col1": "New row", "col2": random.randint(0, 1000)}]
        return new_data, html.Pre(str(new_data))
    else:
        raise PreventUpdate

grid.py

from dash import html, dash_table, Input, Output, callback, register_page, State


register_page(__name__)


layout = html.Div([
    html.H3("Data tables"),
    dash_table.DataTable(
        id="table",
        row_deletable=True,
        column_selectable="single",
        page_size=5,
        persistence=True,
        persisted_props=[
            "data",
            "columns.name",
            "filter_query",
            "hidden_columns",
            "page_current",
            "selected_columns",
            "selected_rows",
            "sort_by",
        ],
    ),
])


@callback(
    Output("table", "data"),
    Input("store", "data"),
    State("table", "data"),
)
def update(new_data, existing_data):
    if existing_data is not None:
        return existing_data + new_data
    else:
        return new_data

If you create the table as a page component, I believe it should work as intended,

https://www.dash-extensions.com/sections/pages

1 Like

Thanks @Emil , I have checked this approach, but stumbled across two issues:

  1. Dash complained that the table id wasn’t registered. Do you know of any examples of using the extension with callbacks?
  2. I don’t need the component on both pages, only on the grid.py.

Would you know how to modify my example to correctly use the extension?

  1. Did you add the setup_page_components() call as part of your layout?
  2. Then just add it on that page

@Emil Yes, I did.

In the meantime, I tried using Store to for keeping the state of DataTable and, to my surprise, it worked. I was expecting hitting a circular dependency problem, where we would run into an infinite look of:

  • Removing a row triggers event that updates the data in Store
  • Store update triggers update of DataTable
  • DataTable update triggers Store update…

Here’s the new code for grid.py:

from dash import html, dash_table, Input, Output, callback, register_page


register_page(__name__)


layout = html.Div([
    html.H3("Data tables"),
    dash_table.DataTable(
        id="table",
        data=[{"name": "Test", "label": "Test"}],
        row_deletable=True,
        column_selectable="single",
        page_size=5,
        persistence=True,
        persisted_props=["columns.name", "data"],
    ),
])


@callback(
    Output("table", "data"),
    Input("store", "data"),
)
def add_rows(new_data):
    return new_data

@callback(
    Output("store", "data", allow_duplicate=True),
    Input("table", "data"),
    prevent_initial_call=True
)
def update_back(new_data):
    return new_data

Does Dash do something smart about the circular dependency? Or maybe there’s no circular dependency?

I would have expected that code to give an error. At least, in previous versions of Dash it would :slight_smile:

Would you post the code using page components that didn’t work? I am curious what went wrong

@Emil It looks like I must have made a mistake earlier, dash_extensions.pages works fine too. Thanks for your help!

For other people who stumble upon a similar issue, I include links to both solutions:

  1. Shared store that somehow is not throwing circular dependency exception: link
  2. Using a persistent component link

Any ideas why no 1 is working?