Skip to content
This repository has been archived by the owner on Jun 4, 2024. It is now read-only.

Bugs with pagination/selections when number of data rows changes #924

Open
samlishak-artemis opened this issue Jul 14, 2021 · 2 comments
Open

Comments

@samlishak-artemis
Copy link

samlishak-artemis commented Jul 14, 2021

A few different bugs can be demonstrated with the following app.

It just shows a button and a table; the table has 10 rows if the button has been clicked an odd number of times, and 30 otherwise. The table shows 10 rows per page. Each row has an ID, which is incremented by 1 for every row when there are 30 rows, and incremented by 2 otherwise.

import random

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

app = dash.Dash(__name__)


COLUMNS = ["id", "a", "b", "c", "d"]

app.layout = html.Div(
    [
        html.Button(
            "Change data",
            id="change",
            n_clicks=0,
        ),
        dash_table.DataTable(
            id="table",
            columns=[{"name": x, "id": x} for x in COLUMNS],
            page_action="native",
            page_size=10,
            row_selectable="multi",
        ),
    ]
)


@app.callback(
    Output("table", "data"),
    Input("change", "n_clicks"),
)
def change_data(n_clicks):
    if n_clicks % 2:
        rows = 10
        id_step = 2
    else:
        rows = 30
        id_step = 1

    return [generate_row(i * id_step) for i in range(rows)]


def generate_row(id):
    return {x: f"id-{id}" if x == "id" else random.randint(0, 10) for x in COLUMNS}


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

Table disappears when selected page no longer exists

  1. While the table has 30 rows, navigate to page 2 or 3 of the table
  2. Click the "change" button. There is now only 1 page, so the page navigation does not appear. There is also no data on the current selected (invalid) page, so no data is shown either, just the column headers.

There doesn't appear to be a way out of this situation. Maybe it could be fixed by a workaround where a callback updates page_current based on data and page_size, but it might be neater if it was automatically handled by the component.

Uncaught TypeError: Cannot read property 'id' of undefined after a row has been selected then removed

  1. While the table has 30 rows, navigate to page 2 or 3 of the table
  2. Tick some of the rows
  3. Navigate back to page 1 of the table
  4. Click the "change" button
  5. Tick one of the rows. No tickbox appears and the following error is written to the console:
Uncaught TypeError: Cannot read property 'id' of undefined
    at operations.tsx:32
    at r (_map.js:7)
    at map.js:62
    at _dispatchable.js:44
    at Object.t (_curry2.js:28)
    at onChange (operations.tsx:32)
    at HTMLUnknownElement.callCallback (react-dom@16.v1_9_1m1617985068.14.0.js:182)
    at Object.invokeGuardedCallbackDev (react-dom@16.v1_9_1m1617985068.14.0.js:231)
    at invokeGuardedCallback (react-dom@16.v1_9_1m1617985068.14.0.js:286)
    at invokeGuardedCallbackAndCatchFirstError (react-dom@16.v1_9_1m1617985068.14.0.js:300)

I think this is because selected_rows contains row indices that are outside of the current data. Again, maybe it could be worked around with a callback to update selected_rows based on data.

Incorrect rows remain selected when data changes

  1. While the table has 30 rows, select the rows with ID "id-2" and "id-4"
  2. Click the "change" button. Now the rows with ID "id-4" and "id-8" are selected, but selected_row_ids is still ["id-2", "id-4"].

selected_rows appears to be the "primary" way of remembering which rows are selected, and selected_row_ids just follows on from that. In this situation, it seems like using storing the row IDs would make more sense, as this example represents a sort of filtering action where some rows have been removed from the table, so the user would want the same row IDs selected after filtering.

Having selected_row_ids as the "primary" way of storing the selected rows rather than selected_rows also fixes a related issue I'm having where I want to modify selected_row_ids from another callback, but it won't actually update the tickboxes because selected_rows isn't changing.

@samlishak-artemis samlishak-artemis changed the title Bugs with pagination when number of data rows changes Bugs with pagination/selections when number of data rows changes Jul 14, 2021
@ismafoot
Copy link

Hi, I've encountered a similar issue:
Context:
I have a DataTable that gets updated by two different dropdown callbacks with multi select enabled.
Let's say that based on my dropdrown values, I have 4 rows showing in the table and I select the last row.

When I change a value in one of my dropdowns, my table now shows 2 rows. If I try to select one of those rows, I can't.

In order to make it work , I have to go back to the previous dropdown value combinations, then unselect that row and then change the dropdowns.

Chrome Error Message:
image

Sample data:


import dash

import dash_core_components as dcc

import dash_html_components as html

from dash.dependencies import Input, Output

import pandas as pd 

from dash_table import DataTable

app = dash.Dash()

country = pd.Series(['germany', 'france','germany', 'france'])

city = pd.Series(['munich', 'paris','berlin', 'lyon'])

pop = pd.Series([100, 200,300, 400])

frame = pd.DataFrame({'country': country, 'city':city, 'pop': pop})

app.layout = html.Div([

        html.Div([

            dcc.Dropdown(

            id='country',

            value = 'germany',

            clearable=False,

            options = [{'label': str(c), 'value': c} for c in frame.country.unique()],

            multi=False

        ),

        dcc.Dropdown(

            id='city',

            clearable=True,

            multi=True

        )]),

        html.Div([

            DataTable(

                id='tbl',

                columns=[{"name": "...", "id": "..."}],

                row_selectable='multi'

                )])

])

# populate simulation dropdown filter

@app.callback(

    Output('city', 'options'),

    Input('country', 'value')

)

def update_filter(country):

    new_frame = frame[(frame.country == country)]

    opt = [{'label': str(c), 'value': c} for c in new_frame.city.unique()]

    return opt

@app.callback(

    Output('tbl', 'columns'),

    Output('tbl', 'data'),

    Input('country', 'value'),

    Input('city', 'value')

)

def update_table(country, city):

    # session descriptive info

    new_frame = frame[(frame.country == country) & (frame.city.isin(city))]

    cols = [{'name': i.capitalize(), 'id': i} for i in new_frame.columns]

    data = new_frame.to_dict('records')

    return cols, data

if __name__ == '__main__':

    app.run_server(debug=True)

@nielsdewitte420
Copy link

nielsdewitte420 commented Jun 22, 2022

@AnnMarieW Are there any updates on the issue relating update/removing rows and it's relationship with selected row attribute? Feels like this can be a real bottleneck for applications.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants