AgGrid pre_selected_rows and st.query_params not working properly together

Hello everyone.

In my local streamlit app, the AgGrid table seem to behave strangely when I attempt to update the url query parameter with selected items. The strange behaviors seems non-deterministic

  • Most often, the paginated table would reload back to the first page instead of whichever page I was when I made the selection
  • In some rare occasion especially after I have made a number of selections in a row, the table would refresh several time on its own

Ultimately, the desire behavior is having my user load the page with some query parameter and have the table preselect those once load. As user changes the table selections, the url query parameter should update accordingly.

Anyone else able to get something like this to work?

package versions
streamlit==1.30.0
streamlit-aggrid==0.3.4
pandas==2.0.2
numpy==1.24.3

code

import streamlit as st

import pandas as pd
import numpy as np
from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode, JsCode

import time

st.set_page_config(
    page_title="test",
    initial_sidebar_state="collapsed",
)


@st.cache_data
def get_df():
    np.random.seed(42)
    data = {
        'Column1': np.arange(1, 41),
        'Column2': np.random.randint(1, 100, size=40),
        'Column3': np.random.choice(['A', 'B', 'C'], size=40),
        'Column4': np.random.randn(40),
        'Column5': np.random.uniform(0, 1, size=40)
    }
    return pd.DataFrame(data)

df = get_df()

pre_selected_items = []
if "item" in st.query_params:
    pre_selected_items = [int(i) for i in set(st.query_params.get_all("item"))]


if "my_table" in st.session_state:
    pre_selected_items = set([row["Column1"] for row in st.session_state.my_table.get("selectedRows", [])])

    # update the url according to job selection
    if len(pre_selected_items) > 0:
        st.query_params.item=pre_selected_items
    else:
        st.query_params.clear()

st.header("AgGrid", divider="gray")

def build_table(df, pre_selected_items):
    # setting up the dataframe
    test_df = df.copy()

    # configure the AgGrid table UI
    builder = GridOptionsBuilder.from_dataframe(test_df)
    builder.configure_pagination(enabled=True, paginationAutoPageSize=False, paginationPageSize=10)
    pre_selected_rows={
        str(index): True 
        for index in test_df.index[test_df["Column1"].isin(pre_selected_items)].tolist()
    }
    builder.configure_selection("multiple", use_checkbox=True, header_checkbox=True, pre_selected_rows=pre_selected_rows)
    builder.configure_selection("multiple", use_checkbox=True, header_checkbox=True)
    
    # rendering the table
    return AgGrid(
        test_df, 
        builder.build(), 
        update_mode=GridUpdateMode.SELECTION_CHANGED, 
        fit_columns_on_grid_load=True,
        allow_unsafe_jscode=True,
        reload_data=False,
        key="my_table"
    )
my_table = build_table(df, pre_selected_items)

You should post issues in aggrid github as well.

I don’t think third party components can keep up with streamlit’s rapid development. Perhaps there are a couple of them. Unless absolutely needed, I try to avoid these components.

It can be bothersome if your app is in production. At some point we also need to support the component developer financially.

Hello @Anh_Ngo,

Given the limitations, here’s a strategy that might help:

  1. Ensure that updates to st.query_params or st.session_state are made conditionally to avoid unnecessary reruns.

  2. Try to minimize or manage reruns triggered by selection changes. One approach could be debouncing updates to query params or updating them only under specific conditions to prevent constant reruns.

  3. Your approach to use st.session_state for maintaining selections is correct. However, ensure that you’re not causing reruns by updating the session state in a way that doesn’t directly affect the UI or user’s interaction flow.

  4. Use st.write(st.session_state) and st.write(st.query_params) at various points in your app to understand how state is being updated throughout the app’s lifecycle (can provide insights into unexpected behavior or state changes)

import streamlit as st
import pandas as pd
import numpy as np
from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode

df = get_df()

if "selected_items" not in st.session_state:
    st.session_state.selected_items = set()

if "item" in st.query_params and not st.session_state.selected_items:
    st.session_state.selected_items = set(int(i) for i in st.query_params["item"])

def on_selection_changed(selected_rows, selected_row_data):
    selected_ids = {row['Column1'] for row in selected_row_data}
    st.session_state.selected_items = selected_ids
    st.experimental_set_query_params(item=list(selected_ids))

def build_table(df):
    gb = GridOptionsBuilder.from_dataframe(df)
    gb.configure_pagination()
    gb.configure_selection('multiple', use_checkbox=True, pre_selected_rows=st.session_state.selected_items)
    grid_options = gb.build()
    grid_response = AgGrid(
        df,
        gridOptions=grid_options,
        update_mode=GridUpdateMode.SELECTION_CHANGED,
        on_selection_changed=on_selection_changed,
        key='my_grid'
    )
    return grid_response

build_table(df)

Hope this helps!

Kind Regards,
Sahir

P.S. Lets connect on LinkedIn!