Dash datatable use selected rows data on button click

I am trying to use the selected rows feature of dash datatable to perform something on click of a button but I can’t seem to extract any selected row data. This is my current code:

html.Div([html.H3("Table of unverfied users", style = {"color":"grey"}),
                 dash_table.DataTable(id = "email-auth",
                                 columns = [{"name":i, "id":i} for i in columns_to_verify],
                                 data = users_to_verify.to_dict("records"),
                                #for page size i.e. pages page_size = 10
                                  page_size = 50,
                                 page_action = "none",
                                 #add row selectivity
                                 row_selectable = "multi",
                                 #store selected rows
                                 selected_rows = [],
                                  fixed_rows = {"headers":True},
                                  style_header = {"backgroundColor":"blue",
                                                  "color": "white",
                                                  "border":"1px solid black"},
                                  style_cell = {"textAlign":"left",
                                                "border":"1px solid black"},
                                 style_table={ "overflowY":"auto",
                                                "overflowX":"auto"})],
                style = {"display":"block",
                        "width":"80%",
                        "marginTop":10,
                        "marginBottom":40,
                        "margin-left":"auto",
                        "margin-right":"auto",
                        "padding":"10px"}),

    html.Div([
    html.Button("Verify", id = "but-veify", n_clicks = 0),

    html.Button("Remove", id = "but-remove", n_clicks = 0),

    html.Div(id = "hidden-div", style = {"display":"none"})])

    @app.callback(
  Output("hidden-div", "style_data_conditional"),
  [Input("email-auth", "columns"),
  Input("email-auth", "selected_rows")]
)
def update_styles(columns, selected_rows):
  print("I am triggered")
  # return[{"if":{"derived_virtual_indeces":i},
  # "background_color":"grey"
  # } for i in selected_rows]
  selected_rows = [columns[i] for i in selected_rows]
  print(selected_rows)
  style = {"display":"none"}
  return style

Ideall I would like to tale selected row data, i.e. user id’s from each row, then on click of either verify or remove it would perform a given action i.e. On click of Verify, all ids would be verified such as:

def verify(user_df):
     user_ids = list(user_df["uid"])
     ref = db.reference("/users/")
     for user_id in user_ids:
         ref.child(user_id).child("Verified").set(True)

While for Remove it would be:

def delete_users(user_df):
    user_ids = list(user_df["uid"])
    for user_id in user_ids:
        auth.delete_user(uid)

When the selected rows are selected and the button is clicked. Any advice would be appreciated.

Hello there, @Philip08!

If I understand you correctly, you want to use the selected columns in a callback just when one of the two buttons are clicked, right? So, just a few points:

  • Input("email-auth", "selected_rows") returns the row numbers for each of the selected rows in an array, not the rows themselves. I imagine you noticed it when debugging and you can of course filter the dataframe by row, just be careful when it is dynamically updated (which I imagine is your target here, but for now it seems like a global object).

  • If my assumption is right, your verification callback should look more or less like this:

@app.callback(
    Output("hidden-div", "children"),
    Input("but-verify", "n_clicks"),
    State("email-auth", "selected_rows"),
    prevent_initial_call=True,
)
def verify(n_clicks, selected_rows):
    print("Verify triggered")
    # those are the rownumbers in users_to_verify 
    # if users_to_verify is not dynamic, just filter
    # it by row number and grab user_ids or other cols
    print(selected_rows) 

    if len(selected_rows) == 0:
       raise dash.exceptions.PreventUpdate

    return "test"

You want to use State for the selected rows since updates on the selection should not trigger the callback, just the button click. A similar structure should be followed by the remove, however you have to update the table data as well and you should consider something like dcc.Store() to persist its value.

Besides, you might want to handle the case of empty selections in the callback function differently and you might want to unset the click counter in the component definition to use prevent_initial_call=True (won’t trigger the callback when the application loads).

Hope that helps and happy to elaborate more if needed!

1 Like

Hi @jlfsjunior!

Thanks for that! Slowly getting there with the understanding of this.

A few things adding on to your answer:

  1. If I wanted to update the conditional formatting of the dash_table, instead of State I could use the Input and it would update (i.e. highlight the row) whenever a new row is selected?
  2. If I did this but had if conditions for clicks, the clicks would only triger when those if conditions are clicked?
  3. The if conditions would change the dash_table but this is based on some external data source so instead of store should I just update the data based on the new data from the external url or would store be better to manage it? Would it also be possible to update this periodically?
  4. I have added the buttons into ConfirmDialogProviders since, as I want to make sure the user is clear that they want to proceed. which shouldn’t affect click behaviour in this function should it?
  5. Would it be better to split up functions so that I have one for highlighting the row on rows selected (i.e. conditional), one for click of verify, one for click for remove? (I tried this but it stopped highlighting the rows when I separated them out but not sure why)

Apologies for all the follow up questions but your initial answer helped so thank you!

1 Like

No problem at all, glad to help!

About your questions, let me do my best to answer them:

  1. If I wanted to update the conditional formatting of the dash_table, instead of State I could use the Input and it would update (i.e. highlight the row) whenever a new row is selected?

Exactly. You got the idea. A different callback with the selected rows as Input, not State.

  1. If I did this but had if conditions for clicks, the clicks would only triger when those if conditions are clicked?

I will assume you are talking about your “validation callback” here, but the question is quite general and so is the answer: if you want to add any logic to different clicks, this has to be implemented in the callback functions. In other words, a Button will always update its click count when clicked (exceptions are when the page is reloaded and the click count is not persistence, then you need Store of “session” type or equivalent… but I digress).

  1. The if conditions would change the dash_table but this is based on some external data source so instead of store should I just update the data based on the new data from the external url or would store be better to manage it? Would it also be possible to update this periodically?

I am not sure I understand how the external data is related to the application. Is this a database table that you intend to update using the app? There are different options depending of what you need and I can point out to community examples that you might find useful when I understand it more.

  1. I have added the buttons into ConfirmDialogProviders since, as I want to make sure the user is clear that they want to proceed. which shouldn’t affect click behaviour in this function should it?

No impact on the click behavior. You can use them.

  1. Would it be better to split up functions so that I have one for highlighting the row on rows selected (i.e. conditional), one for click of verify, one for click for remove? (I tried this but it stopped highlighting the rows when I separated them out but not sure why)

I think you must have the three callback separated because they are triggered by different components. The two buttons should both use the selection as State, so they can use the array to complete their task. The row highlighting on the other hand needs to be triggered every time the selection changes, so it must be on the Input.

Let me know if this clarifies your questions.

1 Like

Hi @jlfsjunior thank you again!

  1. That’s great okay thank you!

  2. Great again thank you!

  3. So the dash_table here is created from data from a firebase realtime database. My next challenge is firstly figuring out how to refresh the data periodically from the data base (i.e. once every day), and secondly refreshing the dash_table after a button click event when data has either been deleted from the firebase database or whether new data has been added (i.e. verified). For example, The current dash_table contains information on users who have not yet been verified (i.e. a column in firebase says Verified = False). The purpose of the click buttons would be for 1) to verify all clicked rows, so that this would update the firebase database with Verified = True and then they can be removed from the dash_table, or 2) for the users to be deleted from the firebase_database, which would need the dash table to be updated. I have the functions and I know what data to feed them (from the State condition), but it is just being able to update the dash_table periodically or on click that is my next challenge. If you have any examples to hand that would be great!

  4. Perfect thank you!

  5. Again perfect thank you! Is there any reason why splitting them up as you say would affect the performance of either one? I tried splitting up the style_data_conditional from the first button click callback, but the style_data_conditional no longer worked with the callbacks as:

@app.callback(
  Output("email-auth", "style_data_conditional"),
  Input("email-auth", "selected_rows")
  #prevent_initial_call = True
)
def update_styles(selected_rows):
 
  print(selected_rows)
  table_style = [{"if": {"row_index":i}, "background_color":"#F0F8FF"} for i in selected_rows]

  return table_style  

and

@app.callback(
  Output("email-auth", "style_data_conditional"),
  Input("but-verify", "n_clicks"),
  State("email-auth", "selected_rows"),
  prevent_initial_call=True
)
def update_data(verify_clicks, selected_rows):
  print("I am triggered")

  if selected_rows:
    user_ids = users_to_verify.iloc[selected_rows]
    user_ids = list(user_ids["User id"])
    print(user_ids)

  if verify_clicks:
    for user_id in user_ids:
      try:
        ref.child(user_id).child("Verified").set(True)
      except:
        print("User cannot be updated")

  return "test"

On point 3, I don’t have any experience with Firebase and it would probably better asking in another post, so the context is more specific to it…

On point 5, your snippet is not working because you have both callbacks updating the same prop of the same component:

Output("email-auth", "style_data_conditional")

Plus, the second callback is probably broken, as you are returning “test” to style_data_conditional, which expects a dict or array (probably, haven’t checked it) or at least a valid style. If you want to clear style_data_conditional in the second callback, then you can try to return an array with an empty dict and same len as selected_rows, but I was assuming that you probably want to send a different feedback for the user (which rows are valid or the updated data).

Does it make sense?

1 Like

Yes that makes sense! Thank you so much for your help!

1 Like