DataTable derived_virtual_data leaking to wrong table

Hi all,

I ran into a weird issue where the derived_virtual_data of one DataTable actually shows up as the derived_virtual_data of another DataTable!

My application is a quite complicated multi-tab monstrosity with querystrings to preserve the state and make the state shareable (can post some code on how to make shareable state-based urls if people are interested).

The problem only happens when you load the page with a certain state on a certain tab. The gif below shows the behaviour: the html.Div below the DataTable on Tab A actually displays the derived_virtual_data from Tab B and vice versa:

com-video-to-gif

Once you adjust the data again with a callback (in this case from the dropdown) the issue fixes itself. However it ideally should already work on first load :slight_smile:

Below is the code to reproduce. Any suggestions on how to fix?

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

import pandas as pd

app = dash.Dash()
app.config['suppress_callback_exceptions']=True

def tab_a_layout(state, options, columns):
    return html.Div([
        dcc.Dropdown(id='tab_a_dropdown', options=[{'label': i, 'value': i} for i in options], value=state),
        dash_table.DataTable(
                id='tab_a_table',
                columns=[{"name": i, "id": i} for i in columns],
                editable=True,
                sorting=True,
                sorting_type="single",
                row_selectable="single",
                selected_rows=[],
            ),
        html.Div(id='tab_a_output')

    ])

def tab_b_layout(state, options, columns):
    return html.Div([
        dcc.Dropdown(id='tab_b_dropdown', options=[{'label': i, 'value': i} for i in options], value=state),
        dash_table.DataTable(
                id='tab_b_table',
                columns=[{"name": i, "id": i} for i in columns],
                editable=True,
                sorting=True,
                sorting_type="single",
                row_selectable="single",
                selected_rows=[],
            ),
        html.Div(id='tab_b_output')

    ])


def tabs_layout(tab):
    return dcc.Tabs(id='tabs', value=tab, children = [
        dcc.Tab(label='Tab A', id='tab_a', value='tab_a', children = tab_a_layout('A', ['A', 'B'], ['tab_a_col1', 'tab_a_col2'])),
        dcc.Tab(label='Tab B', id='tab_b', value='tab_b', children = tab_b_layout('B', ['A', 'B'], ['tab_b_col1', 'tab_b_col2']))
        ])

app.layout = html.Div([
    tabs_layout('tab_b')
])

@app.callback(
    Output('tab_a_table', 'data'),
    [Input('tab_a_dropdown', 'value')],
    [State('tabs', 'value')])
def callback_a(dropdown_value, tab):
    if dropdown_value=='A':
        return pd.DataFrame({'tab_a_col1': ['A','A'], 'tab_a_col2':['A','A']}).to_dict('rows')
    if dropdown_value=='B':
        return pd.DataFrame({'tab_a_col1': ['B','B'], 'tab_a_col2':['B','B']}).to_dict('rows')

@app.callback(
    Output('tab_a_output', 'children'),
    [Input('tab_a_table', 'derived_virtual_data')],
    [State('tabs', 'value')])
def callback_b(rows, tab):
    return str(rows)

@app.callback(
    Output('tab_b_table', 'data'),
    [Input('tab_b_dropdown', 'value')],
    [State('tabs', 'value')])
def callback_a(dropdown_value, tab):
    if dropdown_value=='A':
        return pd.DataFrame({'tab_b_col1': ['A','A'], 'tab_b_col2':['A','A']}).to_dict('rows')
    if dropdown_value=='B':
        return pd.DataFrame({'tab_b_col1': ['B','B'], 'tab_b_col2':['B','B']}).to_dict('rows')

@app.callback(
    Output('tab_b_output', 'children'),
    [Input('tab_b_table', 'derived_virtual_data')])
def callback_b(rows):
    return str(rows)

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

For those running into similar issues in the future: I fixed it by checking whether specific expected columns are present in virtual_dervied_data and when they are not I display from data:

@app.callback(
    Output('tab_a_output', 'children'),
    [Input('tab_a_table', 'derived_virtual_data'),
     Input('tab_a_table', 'data')],
    [State('tabs', 'value')])
def callback_b(virtual_rows, rows,  tab):
    if virtual_rows is not None and len(virtual_rows)>0 and 'tab_a_col1' in virtual_rows[0].keys():
        return str(virtual_rows)
    else:
        return str(rows)

Thanks for reporting! I suspect that this issue will be fixed with https://github.com/plotly/dash-renderer/pull/101