Hi everyone!
I am a Dash practitioner and a huge advocate for the library.
These days I am trying to implement a multipage webapp and I am having a hard time to “store” changes in components when switching back and forth across pages. Namely, each time I move to a page, the layout is refreshed.
Below there is a (minimal?) code example for reproducibility purpose, where in page 1 I have a html table with the first row allowing for user input and add-validation button, then it is possible to remove the inserted row as well.
Page 2 is just a placeholder for switching back and forth.
I guess one strategy could be to have different dcc.Store
s and record what happens, and populate pages from that when switching. But I hope there is a better strategy
index.py
import dash
from dash import html, dcc
import dash_bootstrap_components as dbc
# Dash app
app = dash.Dash(
__name__,
external_stylesheets=[
dbc.themes.FLATLY,
dbc.icons.BOOTSTRAP,
],
use_pages=True,
)
sidebar = dbc.Col(
id="sidebar",
children=[
dbc.Nav(
id="sidebar-nav",
children=[
dbc.NavLink(children="page1", id="page1", href="/page1", active="exact"),
dbc.NavLink(children="page2", id="page2", href="/page2", active="exact")
],
),
],
)
main_content = dbc.Col(
id="main-content",
className="main-content-expanded",
children=dash.page_container,
)
page = dbc.Row(
id="page",
children=[
sidebar,
main_content,
],
)
url = dcc.Location(id="url", refresh=False)
# App layout
app.layout = html.Div(
id="layout",
children=[page, url],
)
if __name__ == "__main__":
app.run_server(
debug=True,
host=0.0.0.0,
port=8080,
)
pages/page1.py
import dash
import dash_bootstrap_components as dbc
from dash import MATCH, Input, Output, Patch, State, callback, html, dcc
from dash.exceptions import PreventUpdate
dash.register_page(__name__, path="/page1", name="Page1")
def make_filled_row(button_clicked: int, name: str) -> html.Tr:
"""
Creates a filled row in the table, with dynamic id using the button_clicked
parameter.
"""
return html.Tr(
[
html.Td(name),
html.Td(
html.Button(
"Delete",
id={"index": button_clicked, "type": "delete"},
className="delete-user-btn",
)
),
],
id={"index": button_clicked, "type": "table-row"},
)
header = [
html.Thead(
html.Tr(
[
html.Th("First Name"),
html.Th(""),
]
),
id="table-header",
)
]
add_row = html.Tr(
[
html.Td(dcc.Input(id="name-input")),
html.Td(
html.Button(
"Add",
id="add-user-btn",
),
style={"width": "150px"},
),
]
)
body = [html.Tbody([add_row], id="table-body")]
table = html.Table(
header + body,
)
# Layout of the page
layout = dbc.Container(
[
dbc.Row([dbc.Col([table])]),
]
)
# List of callbacks for the page
@callback(
[
Output("table-body", "children", allow_duplicate=True),
Output("name-input", "value"),
],
[
Input("add-user-btn", "n_clicks"),
],
[
State("name-input", "value"),
],
prevent_initial_call=True,
)
def on_add(n_clicks: int, name: str):
"""Adds a new row to the table, and clears the input fields"""
table_body = Patch()
table_body.append(make_filled_row(n_clicks, name))
return table_body, ""
@callback(
Output({"index": MATCH, "type": "table-row"}, "children"),
Input({"index": MATCH, "type": "delete"}, "n_clicks"),
prevent_initial_call=True,
)
def on_delete(n_clicks: int) -> html.Tr:
"""Deletes the row from the table"""
if not n_clicks:
raise PreventUpdate
return html.Tr([])
pages/page2.py
import dash
import dash_bootstrap_components as dbc
from dash import html
dash.register_page(__name__, path="/page2", name="Page2")
# Layout of the page
layout = dbc.Container(
[
dbc.Row([dbc.Col(html.Div("You are on page 2"))]),
]
)