How to reset Dash DataTable `selected_cells` varaible to None from within a callback

  1. Having a DataTable on my app, it operates as expected. I import the selected_cells field from the datatable into the callback and it is initially None because nothing has been clicked on yet.
  2. Then I click on a cell and the print statement i have set up shows me the correct values of what i clicked on.
  3. However I am running into an issue with reverting the selection back to None so that the cell de-selects.

Can anyone help with how to get that None value back to the DataTable from the callback?

Excerpt from the Dash Docs: (Reference | Dash for Python Documentation | Plotly)

selected_cells ( dict ; optional): selected_cells represents the set of cells that are selected, as an array of objects, each item similar to active_cell . Multiple cells can be selected by holding down shift and clicking on a different cell or holding down shift and navigating with the arrow keys. selected_cells has the following type: list of dicts containing keys ‘row’, ‘column’, ‘row_id’, ‘column_id’. Those keys have the following types:

  • row (number; optional)
  • column (number; optional)
  • row_id (string | number; optional)
  • column_id (string; optional)

Hi @cgeorge

Here is a small example – it clears selected cells (and the active cell) on a button click:


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

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

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        html.Button("clear selection", id="clear"),
        dash_table.DataTable(
            id="table",
            data=df.to_dict("records"),
            columns=[{"name": i, "id": i} for i in df.columns],
        ),
    ]
)

@app.callback(
    Output("table", "selected_cells"),
    Output("table", "active_cell"),
    Input("clear", "n_clicks"),    
)
def clear(n_clicks):
    return [], None


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

3 Likes

Thanks, I really appreciate it. This is a great concise example.

1 Like

Hi! In case someone arrives here looking for a solution to deselect cells if you have more than one table and the user clicks on another table, I have updated @AnnMarieW 's (great) solution with a pattern-matching callback:

import dash
from dash import html, dash_table, Input, Output, State, ALL, ctx
import plotly.express as px
import json

# ctx only works for dash >= 2.4.0
# from dash import callback_context as ctx # run this if you have dash<2.4

df = px.data.iris()

def table(index, data=df) :
    data = data.iloc[10*index:10*(index+1)]
    return html.Div([
        dash_table.DataTable(
            id={'type':'table', 'index':index},
            columns=[{"name": i, "id": i} for i in data.columns],
            data=data.to_dict("records"),
            ),
        html.Br()
    ])

app = dash.Dash(__name__)

app.layout = html.Div(
    [table(i) for i in range(3)],
    style={'padding':'30px'})

@app.callback(
    Output({'type':'table', 'index':ALL}, 'selected_cells'),
    Output({'type':'table', 'index':ALL}, 'active_cell'),
    Input({'type':'table', 'index':ALL}, 'selected_cells'),
    State({'type':'table', 'index':ALL}, 'id'),
    prevent_initial_call=True
)
def unselect_cells(cells, tables):
    table_ids = [t['index'] for t in tables] 
    
    trigger = ctx.triggered_id.index # only works if you have dash>=2.4.0
    #trigger = json.loads(ctx.triggered[0]['prop_id'].split('.')[0])['index'] # run this if you have dash<2.4.0
    
    new_selection = [[] if t!=trigger else c for c, t in zip(cells, table_ids)]
    new_active = [None if s==[] else s[0] for s in new_selection]

    return new_selection, new_active

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

As Ann Marie noted, It is necessary that both selected_cells and active_cell are updated. They have a slightly different structure: selected_cells is a dictionary with the row information of each selected cell (as there can be more than one). active_cell is just a dict with the row information. As we are using pattern-matching selector ALL, each input/state (cells, tables) is a LIST of the value of those arguments in each of the tables.

1 Like

@app.callback(
[Output(‘tbl’,‘selected_cells’),
Output(“tbl”, “active_cell”)],
Input(‘modal-xl’,‘is_open’)
)
def deSelectCell(is_open):
if not is_open:
return Dash.no_update

print('inside ...........................')
return [],None

Use Modal’s is_open property as an input for deselecting the selected cells