Dash: [BUG] Clientside callback does not trigger update

Created on 5 Dec 2019  路  3Comments  路  Source: plotly/dash

Thank you so much for helping improve the quality of Dash!

We do our best to catch bugs during the release process, but we rely on your help to find the ones that slip through.

Describe your context
I am working on displaying a table that is in sync with a database:

def get_layout(**kwargs):
    df = get_data_rows()  # this queries the database for some data
    data = df.to_dict("records")
    table = dash_table.DataTable(
            filter_action="native",
            sort_mode="multi",
            id=f'my-table',
            columns=[
                {"name": i, "id": i} for i in df.columns],
            data=data,
            fixed_rows={'headers': True, 'data': 0},
            )
    return html.Div(
                id='table-wrapper',
                children=[
                    lo_table,
                    dcc.Interval(
                        id='table-update', interval=update_interval),
                    dcc.Store(id='table-update-first-row-id'),  # this is to store the latest id
                    dcc.Store(id='table-update-new-rows'),   # this is to store the new entries
                    ],
            )

app.clientside_callback(
    ClientsideFunction(
        namespace='my_namespace',
        function_name='array_first_id'
    ),
    Output('table-update-first-row-id', 'data'),
    [Input('my-table', 'data')]
)

app.clientside_callback(
    ClientsideFunction(
        namespace='my_namespace',
        function_name='array_prepend_to'
    ),
    Output('my-table', 'data'),
    [Input('table-update-new-rows', 'data')],
    [State('my-table', 'data')]
)

@app.callback(
        Output('table-update-new-rows', 'data'),
        [
        Input('table-update', 'n_intervals')], [
        State('table-update-first-row-id', 'data'),
        ])
def refresh_table(n_intervals, first_row_id):
    df = get_new_entries_since_id(first_row_id)
    return df.to_dict("records")
// clientside.js
if (!window.dash_clientside) {window.dash_clientside = {};}
window.dash_clientside.my_namespace = {
    array_prepend_to: function(a, b) {
        console.log(a)
        console.log(b)
        b.unshift(...a);
        return b;
    },
    array_first_id: function(a) {
        if (a) {
            return a[0]['id'];
        }
        return null;
    }
}



md5-359bdd7c81a7bc0b7287a1d09df0d636



dash                          1.6.0
dash-bootstrap-components     0.7.2
dash-core-components          1.5.0
dash-html-components          1.0.1
dash-renderer                 1.2.0
dash-table                    4.5.0
  • if frontend related, tell us your Browser, Version and OS

    • OS: [e.g. macOS 10.15.1]
    • Browser [e.g. Safari]
    • Version [e.g. 13.0.3]

Describe the bug

The table does not update although the 'my-table.data' array is shown to be updated in the console.

Expected behavior

As soon as the clientside function array_prepend_to finished executing, the my-table is redrawn to reflect the change.

Screenshots
This is the first few entries of the table after initialization:

Screen Shot 2019-12-04 at 19 57 34

Screen Shot 2019-12-04 at 19 57 15

This is the content of table-update-new-rows.data and my-table.data, printed from with the array_prepend_to function:

Screen Shot 2019-12-04 at 19 56 52

These arrays have 0 and 1000 entries after the table is initialized, and any new item added to the table-update-new-rows through the interval event callback will trigger the array_prepend_to callback which supposedly updates my-table.data. As shown in the log, when I add 4 new entries to the database, the interval callback picks up the 4 entries successfully, and the clientside function triggers correctly, for a moment there are 4 and 1000 entries in the arrays. The next time these get printed, there are 0 and 1004 entries, which means that my-table.data have 1004 entries now. However, the table view does not update as expected. (A "manual" refresh can be manually done by me applying some row filter through the naive filtering and clearing it.)

Screen Shot 2019-12-04 at 19 57 26

The above shows the content of a and b after I add 4 new entries to the database. But the table view stays the same as before without refreshing itself automatically.

dash-type-bug

All 3 comments

I think what's going on here is that, because you're using an in-place update of the data array, via unshift, when we look for changed props to rerender the table, we don't find any so the rerender is skipped. Put another way, at that point the table thinks it has always had that data in it!

In general for state-driven apps it's best to not mutate objects that are already in use. Can you return a.concat(b); instead? That makes a new object so should avoid this problem. It's slightly slower, but for any reasonable size data array that should be insignificant.

I'm glad you brought this up, because this is a really cool use case for clientside callbacks, but we should document the need to treat data structures there immutably (not an issue server-side, at least for callback arguments, since they're all serialized/deserialized to get to/from the browser)

Thank you for the insight. I was also thinking the same. But I thought there were some variable-binding mechanism that would listen to the change. I guess It makes sense to assume immutability since it makes things much easier.

I went on and did a.concat(b), but without success. My guess is that there was some optimization to the function, so no new object is created. I instead used
        c = new Array()
        c.push(...a)
        c.push(...b)
        return c

Edit: After more careful observation, a.concat(b) works as expected.

Thank you very much for the help. I just started using the library, and really like using it. I appreciate all the works that you guys have put into this great software product!

OK great! I'll close this one, but have made https://github.com/plotly/dash-docs/issues/722 to follow up.

Was this page helpful?
0 / 5 - 0 ratings