AG Grid filter persistence with RowModel

Hello,

I have a standard gird with the rowmodel. I set the persistence to true for filterModel, but it does not work in my case.

AgGrid(
                                className=current_app.config["TABLE_CLASS_NAME"],
                                columnDefs=CompanySearchTable._get_column_defs(),
                                columnSize="responsiveSizeToFit",
                                columnSizeOptions={"skipHeader": False},
                                defaultColDef={"filter": True, "floatingFilter": True},
                                dashGridOptions=CompanySearchTable._get_dash_grid_options(),
                                style={"height": "100%", "width": "100%"},
                                id=ids.COMPANY_SEARCH_TABLE,
                                persistence=True,
                                persisted_props=["filterModel"],
                                persistence_type="session",
                            )

where the id is a normal string. I found the cookie in the browser store, but it does not get updated when choosing a filter.

In the example Persistence | Dash for Python Documentation | Plotly, it worked fine. I also use the same option in the infite Row model and it also works perfectly.

I am not sure what I did wrong here. Thank you for the help

Hello @simon-u,

Have you tried it without the floating filter?

Also, I highly recommend using flex for your columns vs responsiveSizeToFit. Flex is designed to auto fit the columns with any adjustments.

This also allows for setting different sizes on the columns.

Hello @jinnyzor

I just tried disabling the floating filter, but it also won’t work.
Furthermore, for my other table I have the floating filters as well and the persistency and it works.

Does it maybe happen because load the data always fresh into the rowdata via callback?

I also checked with a callback if the filter Model gets updated, which works only the cookie is empty.

The log in the console is: {‘anomaly_score’: {‘filterType’: ‘number’, ‘type’: ‘greaterThan’, ‘filter’: 10}}
After the refresh, the log is: {}

While the cookie lo0ks like: [{}]

Do you happen to use this id somewhere else in your app? This could cause some conflicts.

Just checked that, renamed the table and the callback. Seems to work okay with both names, I find the same behaviour:

  1. Load table
  2. Apply filter, they get persisted in the cookie
  3. Refresh, cookie being empty

I checked in the same callback who updates my data:

@callback(
    Output(ids.COMPANY_SEARCH_TABLE, "rowData"),
    Input(ids.COMPANY_SEARCH_TABLE, "id"),
    State(ids.COMPANY_SEARCH_TABLE, "filterModel"),
    prevent_inital_call=True,
)
def update_company_search_table(_id_value: str, filter) -> list[dict[str, Any]]:
    print(filter)
    data = api.get_company_data()
    data["logo_code"] = data[["asx_code", "company_logo"]].values.tolist()

    data = data.to_dict("records")
    return data

I see the filter on the page refresh there, so it seems the data is get loaded from the cookie, but after tha api call and the rowData input, the filter is not applied to the data.

Is the issue how the ID is used as the trigger?
Maybe that does trigger the load more often, even with prevent_inital_call=True.

Or I have some custom cell render. I am not sure if that might cause something.

You said your other grids persist?

Do you have any callbacks updating the filterModel?

I have only one callback that is the one I posted above to load the rowData. I do not update the filterModel myself.

Yes, I do see the filterModel in the cookie. And when I refresh the page the callback is also printing the filters again.
After loading the data, there are no filters.

It does not seem to be custom cellRenders, I disabled them for this gird and nothing changed.

I try to replicate the issue in a minimal example.

Another thing, why are using rowData instead of infinite. Wouldn’t your grid be better with infinite since you are performing the filters on the data?

I’m curious about not passing rowData, that may be where the issue actually lies.

Or… can’t you get the company data when creating the grid if you want the filterModel to keep?

We only have about 3k rows. I thought that using infinite row model wouldn’t be really of use here. The database and dash are hosted on aws, making one query with 3k rows pretty quick. But filtering would still be nice to check the data.

About the callback for updating the rowData. I thought about that as well. But in this modified example it works fine.

import dash
import dash_ag_grid as dag
from dash import Dash, Input, Output, dcc, html, callback, ctx

app = Dash(__name__)

columnDefs = [
    {"field": "make"},
    {"field": "model"},
    {"field": "price"},
]

rowData = [
    {"make": "Toyota", "model": "Celica", "price": 35000},
    {"make": "Ford", "model": "Mondeo", "price": 32000},
    {"make": "Porsche", "model": "Boxster", "price": 72000},
]


app.layout = html.Div(
    [
        dcc.Markdown("Use the 'Update Filter' button to set the `filterModel` "),
        html.Button("Update Filter", id="filter-model-btn", n_clicks=0),
        dag.AgGrid(
            id="filter-model-grid1",
            columnSize="sizeToFit",
            columnDefs=columnDefs,
            defaultColDef={"filter": True, "floatingFilter": True},
            persistence=True,
            persisted_props=["filterModel"],
            dashGridOptions={"animateRows": False},
        ),
    ]
)


@callback(
    Output("filter-model-grid1", "rowData"),
    Input("filter-model-grid1", "id"),
    prevent_inital_call=True,
)
def update_row(id):
    return rowData


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

After some more checking, it appears that the issue might be before already.

I have pages and a layout function which starts like this:

    # ToDo in the future we can add other information here as well for example from the user
    # Wait for the socket to be connected
    if not socket_ids:
        return dmc.Grid(
            [dmc.Center(dmc.Loader(), style={"height": "100%", "width": "100%"})],
            style={"height": "100%", "width": "100%"},
        )

    return full_layout()


def full_layout():
    print("check rerendering")
    return dmc.Grid(....ag.Grid()....

I can see that the layout function is getting called multiple times when refreshing the page with the grid. Still, I use the same way to set up multiple grids in the application, and I only see the issue here.

What you can do for testing, go into the browser and check your sources, go into the ag grid src > fragments > grid.js (similar name) and search for filterModel with the onGridReady.

It may also be under the componentDidUpdate.

But, you can put a breakpoint there and refresh the page. And see what the data is showing there.

I think something is clearing the data out, or you have something else updating it.

I need to dig into it a bit more, but it seems to be related to the row model as well.

The grid works normally, when changing to the infinite row model.
The filters are loaded on page refresh and after coming back to the page.

What version of the grid are you using?

dash_ag_grid==31.0.1

I tried to find the lines which are causing the problem.

  1. componentdidUpdate gets called and finds the old filters. That is the first appearance of the model I see in the logs when I refresh the page.
  2. Afterwards the onFilterChange is called. I also logged the state at that point, where the filters are empty.

Following all the logs of the filter model are {}.

    onFilterChanged() {
        const {setProps, rowModelType} = this.props;
        if (!this.state.gridApi) {
            return;
        }
        const filterModel = this.state.gridApi.getFilterModel();
        const propsToSet = {filterModel};
        if (rowModelType === 'clientSide') {
            propsToSet.virtualRowData = this.virtualRowData();
        }

        setProps(propsToSet);
    }

In the above code after const propsToSet = {filterModel}; after I have filterModel = {}

Before that, It’s still my filters from the previous page refresh.

So it seems something is going wrong during the hydration process of the virtualRowData.

During componentDidUpdate() the filterChange is called with: this.onFilterChanged(true);

It gets and empty filter dict from the const filterModel = this.state.gridApi.getFilterModel(); and pass that over the prefilled I had.

After some more testing with a smaller dataset, I think it’s the combination of updating the row data in a callback and persisting the filter.

If you pass in the below example the row data directly, the behaviour is as expected. If you uncomment the callback and use that to update, then it resets like I had before.

import dash
import dash_ag_grid as dag
from dash import Dash, Input, Output, dcc, html, callback

app = Dash(__name__)

columnDefs = [
    {
        "field": "asx_code",
        "headerName": "ASX Code",
        "editable": False,
    },
    {
        "field": "company_name",
        "headerName": "Company Name",
        "editable": False,
    },
    {
        "field": "industry",
        "headerName": "Industry",
        "editable": False,
    },
    {
        "field": "anomaly_score",
        "headerName": "Anomaly Score",
        "filter": "agNumberColumnFilter",
        "editable": False,
        "valueFormatter": {"function": "d3.format('.2f')(params.value)"},
    },
    {
        "field": "alerts",
        "headerName": "Alerts",
        "editable": False,
    },
]

rowData = [
    {
        "asx_code": "LEG",
        "company_name": "Legend Mining Ltd",
        "logo_code": [
            "LEG",
            "https://s3-ap-southeast-2.amazonaws.com/dhi-disclosures-public-dev/company_logo/LEG.ico",
        ],
        "industry": "Materials",
        "anomaly_score": 10.94,
        "alerts": 10,
    },
    {
        "asx_code": "KNI",
        "company_name": "KUNIKO LIMITED",
        "logo_code": ["KNI", None],
        "industry": "Materials",
        "anomaly_score": 6.98,
        "alerts": 0,
    },
    {
        "asx_code": "SPQ",
        "company_name": "Superior Resources Ltd",
        "logo_code": [
            "SPQ",
            "https://s3-ap-southeast-2.amazonaws.com/dhi-disclosures-public-dev/company_logo/SPQ.ico",
        ],
        "industry": "Materials",
        "anomaly_score": 9.39,
        "alerts": 0,
    },
    {
        "asx_code": "SER",
        "company_name": "Strategic Energy Resources Ltd",
        "logo_code": [
            "SER",
            "https://s3-ap-southeast-2.amazonaws.com/dhi-disclosures-public-dev/company_logo/SER.ico",
        ],
        "industry": "Materials",
        "anomaly_score": 8.04,
        "alerts": 0,
    },
    {
        "asx_code": "NWM",
        "company_name": "Norwest Minerals Ltd",
        "logo_code": ["NWM", None],
        "industry": "Materials",
        "anomaly_score": 12.05,
        "alerts": 0,
    },
]


app.layout = html.Div(
    [
        dcc.Markdown("Use the 'Update Filter' button to set the `filterModel` "),
        html.Button("Update Filter", id="filter-model-btn", n_clicks=0),
        dag.AgGrid(
            id="filter-model-grid1",
            columnSize="sizeToFit",
            columnDefs=columnDefs,
            rowData=rowData,
            defaultColDef={"filter": True, "floatingFilter": True},
            persistence=True,
            persisted_props=["filterModel"],
            dashGridOptions={"animateRows": False},
        ),
    ]
)


# @callback(
#    Output("filter-model-grid1", "rowData"),
#    Input("filter-model-grid1", "id"),
# )
# def update_row_data(trigger):
#    return rowData


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

Probably the push of the new rowData resets the filter.

Yeah.

rowData made a change in how it performs when pushed. It now is handle async, which means that it triggers things out of order.

That’s why I think the filterModel acts like that.

I don’t know if there is a way around this issue. The selectedRows were a nightmare as well.

1 Like

Yes I noticed that as well.

Before I could update the rowData and selected rows in the callback. After I upgraded AG Grid, it wasn’t working properly anymore.

For my issue, I will probably switch to the infinite row model completely for that use case. Having the persistency is more important.

Anyway, thank you for the guidance in finding the problem. At least I know what I can do now :slight_smile:

For sure.

the selectedRows is going to be fixed in the next release.

As far as infinite row model, it is possible to use rowTransaction with sync. This should maintain the filterModel too.

In the infinite row model its working.

I have it in production with the persistence. That’s why I was a bit surprised.

selectedRows: That’s cool! Then I can add it again :smiley:

1 Like