Datatable derived_virtual_data and dcc.loading creates infinite loop

Hi all. I’m writing a dash app, where i’ve got a callback that looks like:

@app.callback(Output(figure_id, 'figure'),
              [Input(datatable_id, 'derived_virtual_data')])
def my_callback(data):

The callback basically updates a map when data in my datatable is changed, filtered, etc., and it works beautifully.

The issue is that if I wrap the div containing my datatable and figure in a dcc.Loading(id="loading_id", children=[ div that contains my datatable and figure ]) my app enters an infinite loop of switching between displaying the Loading animation and refreshing my table and figure.

If I change derived_virtual_data in the callback to just data, the Loading animation works as intended, but of course, I then lose the ability to refresh things based on the table being filtered, etc so that’s not really an option.

Anyone know why this is happening or how to avoid it?

Thanks,
-Ezra

I never figured out a solution to this - anyone? If you want code to see it happening use:

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


df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')

app = dash.Dash(__name__)

app.layout = dcc.Loading(html.Div([
    dash_table.DataTable(
        id='datatable-interactivity',
        columns=[
            {"name": i, "id": i, "deletable": True, "selectable": True} for i in df.columns
        ],
        data=df.to_dict('records'),
        editable=True,
        filter_action="native",
        sort_action="native",
        sort_mode="multi",
        column_selectable="single",
        row_selectable="multi",
        row_deletable=True,
        selected_columns=[],
        selected_rows=[],
        page_action="native",
        page_current= 0,
        page_size= 10,
    ),
    html.Div(id='datatable-interactivity-container')
]))

@app.callback(
    Output('datatable-interactivity', 'style_data_conditional'),
    [Input('datatable-interactivity', 'selected_columns')]
)
def update_styles(selected_columns):
    return [{
        'if': { 'column_id': i },
        'background_color': '#D2F3FF'
    } for i in selected_columns]

PREV_ROWS = []

@app.callback(
    Output('datatable-interactivity-container', "children"),
    [Input('datatable-interactivity', "derived_virtual_data"),
     Input('datatable-interactivity', "derived_virtual_selected_rows")])
def update_graphs(rows, derived_virtual_selected_rows):
    # When the table is first rendered, `derived_virtual_data` and
    # `derived_virtual_selected_rows` will be `None`. This is due to an
    # idiosyncracy in Dash (unsupplied properties are always None and Dash
    # calls the dependent callbacks when the component is first rendered).
    # So, if `rows` is `None`, then the component was just rendered
    # and its value will be the same as the component's dataframe.
    # Instead of setting `None` in here, you could also set
    # `derived_virtual_data=df.to_rows('dict')` when you initialize
    # the component.
    if derived_virtual_selected_rows is None:
        derived_virtual_selected_rows = []

    global PREV_ROWS
    print(PREV_ROWS)

    if PREV_ROWS == rows:
        print ("preventing update")
        raise PreventUpdate
    else:
        PREV_ROWS = rows
        print(f"Updating PREV_ROWS TO: {PREV_ROWS}")

    time.sleep(1)

    dff = df if rows is None else pd.DataFrame(rows)

    colors = ['#7FDBFF' if i in derived_virtual_selected_rows else '#0074D9'
              for i in range(len(dff))]

    return [
        dcc.Graph(
            id=column,
            figure={
                "data": [
                    {
                        "x": dff["country"],
                        "y": dff[column],
                        "type": "bar",
                        "marker": {"color": colors},
                    }
                ],
                "layout": {
                    "xaxis": {"automargin": True},
                    "yaxis": {
                        "automargin": True,
                        "title": {"text": column}
                    },
                    "height": 250,
                    "margin": {"t": 10, "l": 10, "r": 10},
                },
            },
        )
        # check if column exists - user may have deleted it
        # If `column.deletable=False`, then you don't
        # need to do this check.
        for column in ["pop", "lifeExp", "gdpPercap"] if column in dff
    ]


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

This was taken from https://dash.plot.ly/datatable/interactivity - I just added a dcc.Loading around the div.

One more follow up, the easiest way to achieve the loading result is to create a dcc.Loading(html.Div(id="dummy_div")) outside of the div containing your datatable, then having your callback function update the dummy div. This way, when your datatable is loading, the Loading animation is running, even if it’s technically for a different element.

Would still love to see a better solution to this though.