plotly/dash-renderer

rendering dynamic components with inputs that exist on the page

bakirillov opened this issue · 13 comments

Hello.

Doesn't work when is rendered in a tab.
Example:
elif tab == "tab-2":
return(
html.Div([
dash_table.DataTable(
id='table',
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict("rows"),
)
])
)
df is empty DataFrame with 5 columns.
When "tab-2" is selected, the tab changes but the contents of previously selected tab remain.
Could you please look into that?

Also when I override that as suggested in plotly/dash#230 I am unable to show new data in that DataFrame.

Can you upgrade to the latest version of Dash? This should be fixed in 0.29.0: https://github.com/plotly/dash/blob/master/CHANGELOG.md#0290---2018-11-06.

I'm on 0.30.0 so new version does not help.

Also can't select or filter rows despite row_selectable="single" and filtering=True.

OK thanks for checking. Can you create a complete, minimal, reproducable example?

That should be complete example: https://gist.github.com/bakirillov/1c2e838ab70e0248a98b69651760385a
Also FYI:
Dash version: 0.30.0
Dash Table version: 3.1.6
Dash Core Components version: 0.37.2
Dash HTML Components version: 0.13.2

Thanks, taking a look now

It looks like this is actually more of an issue with dash-renderer than the table component. In particular, its an issue with callbacks updating components that don't exist on the page yet: in your example, the DataTable isn't renderered and therefore doesn't get the update from the callback.

Ideally, I think the correct way to solve this would be through having a global store and for that global store to update components as they get rendered. Here's a quick modification of your example:

import dash
import dash_table
import pandas as pd
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, State, Output
import json

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Minimal tab error example"
app.layout = html.Div([
    dcc.Tabs(id="tabs", value="tab-1", children=[
        dcc.Tab(label="Some tab", value="tab-1"),
        dcc.Tab(label="Second tab", value="tab-2")
    ]),
    html.Div(id='table-store'),
    html.Div(id="tabs-content")
])
app.config['suppress_callback_exceptions']=True

@app.callback(
    Output("tabs-content", "children"),
    [Input("tabs", "value")])
def render_content(tab):
    df = pd.DataFrame(
        {"A":[1,2,3], "B": [1,2,3]},
        columns=["A", "B"]
    )
    if tab == "tab-1":
        return(
            html.Div([
                "Dash version: "+dash.__version__, html.Br(),
                "Dash Table version: "+dash_table.__version__, html.Br(),
                "Dash Core Components version: "+dcc.__version__, html.Br(),
                "Dash HTML Components version: "+html.__version__,
                html.Button("Change the data in the table", id="gobutton")
            ])
        )
    elif tab == "tab-2":
        return(
            html.Div([
                dash_table.DataTable(
                    id='table',
                    columns=[{"name": i, "id": i} for i in df.columns],
                    data=df.to_dict("rows"), sorting=True, row_selectable="single",
                    filtering=True
                )
            ])
        )

@app.callback(
    Output("table-store", "children"),
    [Input("gobutton", "n_clicks")]
)
def on_click(n_clicks):
    df = pd.DataFrame(
        {"A":[5,6,7], "B": [5,6,7]},
        columns=["A", "B"]
    )
    if n_clicks:
        return json.dumps(df.to_dict("rows"))


@app.callback(
    Output("table", "rows"),
    [Input("table-store", "data")]
)
def update_rows(rows):
    print(rows)
    return json.loads(rows)



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

However, dash-renderer doesn't take into account existing components dependencies when renderering new ones. That seems like a bug to me but it'll require some more careful consideration.

That did not solve anything. The example still doesn't work.
And I don't understand why I can neither select nor filter the table despite it being selectable and filterable.

Selecting and filtering worked with dash-table-experiments (without tabs).

That did not solve anything.

Sorry, I wasn't clear enough. I know that it doesn't solve anything - this is a bug in dash-renderer that we need to fix in order for that example to work.

Ok, got it. Good luck!)

The example @chriddyp posted above had some errors, but those became obvious when I tried to run it just now, and after I fix them this all seems to work as you'd expect - so it looks to me as though this bug has been fixed accidentally sometime in the last 6 months? Here's the updated example, the table initially has data 1,2,3 but after clicking the update button in the other tab it has 5,6,7 when I switch back:

import dash
import dash_table
import pandas as pd
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import json

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Minimal tab error example"
app.layout = html.Div([
    dcc.Tabs(id="tabs", value="tab-1", children=[
        dcc.Tab(label="Some tab", value="tab-1"),
        dcc.Tab(label="Second tab", value="tab-2")
    ]),
    html.Div(id='table-store'),
    html.Div(id="tabs-content")
])
app.config['suppress_callback_exceptions'] = True

@app.callback(
    Output("tabs-content", "children"),
    [Input("tabs", "value")])
def render_content(tab):
    df = pd.DataFrame(
        {"A": [1, 2, 3], "B": [1, 2, 3]},
        columns=["A", "B"]
    )
    if tab == "tab-1":
        return(
            html.Div([
                "Dash version: "+dash.__version__, html.Br(),
                "Dash Table version: "+dash_table.__version__, html.Br(),
                "Dash Core Components version: "+dcc.__version__, html.Br(),
                "Dash HTML Components version: "+html.__version__,
                html.Button("Change the data in the table", id="gobutton")
            ])
        )
    elif tab == "tab-2":
        return(
            html.Div([
                dash_table.DataTable(
                    id='table',
                    columns=[{"name": i, "id": i} for i in df.columns],
                    data=df.to_dict("records"),
                    sorting=True,
                    row_selectable="single",
                    filtering=True
                )
            ])
        )

@app.callback(
    Output("table-store", "children"),
    [Input("gobutton", "n_clicks")]
)
def on_click(n_clicks):
    df = pd.DataFrame(
        {"A": [5, 6, 7], "B": [5, 6, 7]},
        columns=["A", "B"]
    )
    if n_clicks:
        return json.dumps(df.to_dict("records"))

@app.callback(
    Output("table", "data"),
    [Input("table-store", "children")]
)
def update_rows(rows):
    print(rows)
    if not rows:
        raise dash.exceptions.PreventUpdate
    return json.loads(rows)

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

So I'm going to close this, but if you're still seeing this or a related problem please open a new issue in the main dash repo, as we're merging dash-renderer in there.