Pass data from stage 1 to stage 2 of my app

Hi,
I am fairly new to Dash. I’ve built my app in two stages which work fine independently but I cannot put them together (on the same page). Here is a minimalist piece of code to demonstrate what I’m trying to do in simple terms. The first stage of my app is a choice of a city (via a dropdown) and the population of a datatable (via the “Initialise app” button). The second stage of the app is an option to edit the data, with the possibility to restore it after any unwanted edits (via the “Restore” button). I don’t know how to put those two stages together. Essentially the first stage provides the data for the second stage to consume it. I’m struggling precisely with that transfer of data or relationship. My simplified code doesn’t work but hopefully you can see what it’s meant to do. Please are you able to edit my code so that it works?

import dash
import pandas as pd
import dash_bootstrap_components as dbc
from dash.dependencies import Input, State, Output

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

city_dropdown = dash.html.Div(
    [
        dash.html.Label("Select city", htmlFor="city"),
        dash.dcc.Dropdown(
            id="city",
            options=[{"label": "NYC", "value": 0}, {"label": "LDN", "value": 1}],
            placeholder="Select city",
        ),
    ]
)


initialise = (dash.html.Button(id="initialise", n_clicks=0, children="Initialise app"),)


tables = dash.html.Div(
    children=[
        dash.html.Label("trade"),
        dash.html.Button("Restore", id="button_e", n_clicks=0),
        dash.dash_table.DataTable(
            id="table_e",
            data=trade.to_dict("records"),
            columns=[{"name": i, "id": i, "type": "numeric"} for i in trade.columns],
            editable=True,
            row_selectable="multi",
        ),
    ]
)

app.layout = dbc.Container(
    [
        dash.html.H2("Test", className="text-center"),
        dbc.Row([dbc.Col(city_dropdown), dbc.Col(initialise)]),
        dbc.Row(tables),
    ],
    fluid=True,
)


@dash.callback(
    Output("trade", "data"),
    Input("initialise", "n_clicks"),
    State("city_dropdown", "value"),
    prevent_initial_call=True,
)
def initialise(n_clicks, city):
    if city == "NYC":
        trade = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    elif city == "LDN":
        trade = pd.DataFrame([[9, 8, 7], [6, 5, 4], [3, 2, 1]])
    return trade


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

Hi @chon ,

When you produce the table trade dataframe is not defined. You can start with an empty table or you can define trade dataframe beforehand. I started with an empty table. Also keep in mind you pass component id and component parameter to a callback. Currently you are passing names. Your condition if city == “NYC” will never be true because the value of your chosen city is either 0 or 1 (options=[{“label”: “NYC”, “value”: 0}, {“label”: “LDN”, “value”: 1}]). I have produced a minimal example:

from dash import (html, dcc, callback, ctx, Input, Output, State, dash_table)

city_dropdown = html.Div(
    [
        html.Label("Select city", htmlFor="city"),
        dcc.Dropdown(
            id="city",
            options=[
                {"label": "NYC", "value": "NYC"},
                {"label": "LDN", "value": "LDN"},
            ],
            placeholder="Select city",
        ),
    ]
)


initialise = (html.Button(id="initialise", n_clicks=0, children="Initialise app"),)

tables = html.Div(
    children=[
        html.Label("trade"),
        html.Button("Restore", id="button_e", n_clicks=0),
        dash_table.DataTable(
            id="table_e",
            columns=[{"name": i, "id": i} for i in ["Column1", "Column2", "Column3"]],
            editable=True,
            row_selectable="multi",
        ),
    ]
)

app.layout = [
        html.H2("Test", className="text-center"),
        city_dropdown,
        initialise,
        tables,
    ]

@callback(
    Output("table_e", "data"),
    Input("initialise", "n_clicks"),
    Input("button_e", "n_clicks"),
    State("city", "value"),
    prevent_initial_call=True,
)
def initialise(n1, n2, city):
    if ctx.triggered_id == "initialise":
        if city == "NYC":
            trade = pd.DataFrame(
                {
                    "Column1": [1, 2, 3],
                    "Column2": [4, 5, 6],
                    "Column3": [7, 8, 9],
                }
            )
        elif city == "LDN":
            trade = pd.DataFrame(
                {
                    "Column1": [9, 7, 8],
                    "Column2": [6, 5, 4],
                    "Column3": [3, 2, 1],
                }
            )
        return trade.to_dict("records")

    else:
        return None

Restore button empties the dataframe. Single user actions are harder to track and would need maybe a dcc.Store compononet or something similar.

Maybe try writting everything out like this for starters:

initialise = (html.Button(id="initialise", n_clicks=0, children="Initialise app"),)

Input(component_id="initialise", component_property="n_clicks"),

@dashamateur Thank you for your response. I’m trying to run your code but it doesn’t work for me. Is it missing anything? The error message I get is

NameError: name 'app' is not defined

I then added the following at the top

app = dash.Dash(__name__)

and the following at the bottom

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

Then the error message was

NoLayoutException: Only strings and components are allowed in a list layout.

I then tried editing the bit that defines the app.layout like this

app.layout = dash.html.Div(
    children=[
        html.H2("Test", className="text-center"),
        city_dropdown,
        initialise,
        tables,
    ]
)

Then the error message was

The children property of a component is a list of lists, instead of just a list.

Please could you let me know how to run your code?

@chon focus on errors and try to fix them specificaly, you will debug alot faster that way

NoLayoutException: Only strings and components are allowed in a list layout.

is pointing to this button:

initialise = (dash.html.Button(id="initialise", n_clicks=0, children="Initialise app"),)

You have to remove outer brackets:

initialise = dash.html.Button(id="initialise", n_clicks=0, children="Initialise app")

This should work if you copy/paste and have the right libraries installed:

import dash
from dash import html, dcc, callback, ctx, Input, Output, State, dash_table
import pandas as pd


app = dash.Dash(__name__)
city_dropdown = html.Div(
    [
        html.Label("Select city", htmlFor="city"),
        dcc.Dropdown(
            id="city",
            options=[
                {"label": "NYC", "value": "NYC"},
                {"label": "LDN", "value": "LDN"},
            ],
            placeholder="Select city",
        ),
    ]
)


initialise = html.Button(id="initialise", n_clicks=0, children="Initialise app")

tables = html.Div(
    children=[
        html.Label("trade"),
        html.Button("Restore", id="button_e", n_clicks=0),
        dash_table.DataTable(
            id="table_e",
            columns=[{"name": i, "id": i} for i in ["Column1", "Column2", "Column3"]],
            editable=True,
            row_selectable="multi",
        ),
    ]
)

app.layout = [
    html.H2("Test", className="text-center"),
    city_dropdown,
    initialise,
    tables,
]


@callback(
    Output("table_e", "data"),
    Input("initialise", "n_clicks"),
    Input("button_e", "n_clicks"),
    State("city", "value"),
    prevent_initial_call=True,
)
def initialise(n1, n2, city):
    if ctx.triggered_id == "initialise":
        if city == "NYC":
            trade = pd.DataFrame(
                {
                    "Column1": [1, 2, 3],
                    "Column2": [4, 5, 6],
                    "Column3": [7, 8, 9],
                }
            )
        elif city == "LDN":
            trade = pd.DataFrame(
                {
                    "Column1": [9, 7, 8],
                    "Column2": [6, 5, 4],
                    "Column3": [3, 2, 1],
                }
            )
        return trade.to_dict("records")

    else:
        return None


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

Hi @dashamateur , thank you for your help. It works.