Dash Ag Grid Flickering Issue

I am not sure how to describe the problem.
I have a complex application (unfortunately I cant share the code) - where after I have the option where the user can select a column for removal. So in the callback, when the user selects the column and press remove button - the column gets removed, and the rowdata value also gets updated.
But after that event the ag grid table just continues to flicker.

On the browser console - I see this error

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn’t have a dependency array, or one of the dependencies changes on every render. Error Component Stack

I was hoping to reproduce the error in a smaller side application I created - and as I expected the problem is not there

import dash_ag_grid as dag
from dash import Dash, html, Output, Input, callback, State, no_update
import dash_mantine_components as dmc
import pandas as pd

app = Dash(__name__, url_base_pathname="/")

data = [
    {
        "localTime": "5:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "5:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "5:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "5:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
    {
        "localTime": "6:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "6:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "6:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "6:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
    {
        "localTime": "7:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "7:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "7:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "7:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
    {
        "localTime": "8:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "8:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "8:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "8:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
    {
        "localTime": "8:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "8:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "8:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "8:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
]

columnDefs = [
    {"field": "localTime"},
    {"field": "a"},
    {"field": "b"},
    {"field": "c"},
    {"field": "d"},
]

app.layout = dmc.MantineProvider(
    html.Div(
        [
            dag.AgGrid(
                id="grid",
                rowData=data,
                columnDefs=columnDefs,
                defaultColDef={
                    "resizable": True,
                    "sortable": True,
                    "filter": True,
                    "flex": 1,
                },
                dashGridOptions={"suppressRowTransform": True},
            ),
            dmc.Select(
                value=None,
                data=[x["field"] for x in columnDefs],
                id="column_populate",
                clearable=True,
            ),
            dmc.Button("Remove Column", id="my-button", color="blue"),
        ],
    )
)


@callback(
    [Output("grid", "columnDefs"), Output("grid", "rowData")],
    Input("my-button", "n_clicks"),
    State("column_populate", "value"),
    State("grid", "columnDefs"),
    State("grid", "rowData"),
)
def remove_column(n_clicks, column_name, input_columnDefs, row_data):
    if n_clicks is None or column_name is None:
        return no_update
    data = pd.DataFrame.from_records(row_data)
    data = data.drop(columns=list({column_name, "id"}.intersection(data.columns)))
    data.index.name = "id"
    print(data)
    return [
        col for col in input_columnDefs if col["field"] != column_name
    ], data.to_dict("records")



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

So I know there is somewhere in my code something is happening and I am hoping to get some guidance on how to debug this. I had print commands everywhere to see whether the callback is getting triggered somewhere (in my actual application) - but does not see like those callbacks are getting triggered. Any guidance on appropriate debugging mechanism?

(Again the code attached behaves the correct way - I added this sample to show the functionality I am trying to have in my actual code)

I also tried to check the callback graph - but it does not render

Also, to my horror - the problem does not happen with DASH 2.18.2 - seems like there is a problem somewhere in V3.

Hello @sssaha1989,

Did you make sure to update to the ag grid version that supports dash 3?

I have two python environments (using python 3.13.5) - one with Dash 2.18.2 and another one with the latest version of Dash. Both do have Dash Ag Grid 32.3.0.

Should make sure that whatever the grid is inside in your application isn’t causing the issue.

Maybe perhaps a dcc.Loading or something?

If that was the case - shouldn’t that exist regardless of Dash Version? I do not see the issue with the same code with V 2.18.2 - it only happens with V3.

If you can provide an example of the code and how the flicker is happening, then we can help with debugging it.

Dash 2 and Dash 3 had some changes in how the app is built on the clientside, Dash 3 doesnt need to rebuild everything each time a prop changes, so it could be a symptom of this approach.

@sssaha1989

Are you using any custom DMC components in the grid? If so, what version of DMC are you using?

import dash_ag_grid as dag
from dash import Dash, html, Output, Input, callback, State, no_update
import dash_mantine_components as dmc
import pandas as pd
import dash_bootstrap_components as dbc

app = Dash(__name__,url_base_pathname="/dash_ag_grid_test/")

data = [
    {
        "localTime": "5:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "5:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "5:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "5:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
    {
        "localTime": "6:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "6:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "6:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "6:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
    {
        "localTime": "7:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "7:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "7:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "7:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
    {
        "localTime": "8:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "8:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "8:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "8:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
    {
        "localTime": "8:00am",
        "a": 0.231,
        "b": 0.523,
        "c": 0.423,
        "d": 0.527,
    },
    {
        "localTime": "8:15am",
        "a": 0.423,
        "b": 0.452,
        "c": 0.523,
        "d": 0.543,
    },
    {
        "localTime": "8:30am",
        "a": 0.537,
        "b": 0.246,
        "c": 0.426,
        "d": 0.421,
    },
    {
        "localTime": "8:45am",
        "a": 0.893,
        "b": 0.083,
        "c": 0.532,
        "d": 0.983,
    },
]

columnDefs = [
    {"field": "localTime"},
    {"field": "a"},
    {"field": "b"},
    {"field": "c"},
    {"field": "d"},
]

popover = dmc.Group(
    [
        dmc.Button(
            "Toggle",id="toggle",n_clicks=0,
        ),
        html.Div([], id="target"),
        dbc.Popover(
            [
                dbc.PopoverBody(dmc.Stack(
                [
                    dmc.Select(
                    value=None,
                    data=[x["field"] for x in columnDefs],
                    id="column_populate",
                    clearable=True,comboboxProps={'zIndex':8000}
                ),
                dmc.Button("Remove Column", id="my-button", color="blue",fullWidth=True),
                ]
                    )),
            ],
            id="popover",
            is_open=False,
            target="target",
        ),
    ],align='flex-end', justify='flex-end'
)

ag_grid = dag.AgGrid(
                id="grid",
                rowData=data,
                columnDefs=columnDefs,
                defaultColDef={
                    "resizable": True,
                    "sortable": True,
                    "filter": True,
                    "flex": 1,
                },
                dashGridOptions={"suppressRowTransform": True},
            )

asset_tab = dmc.Tabs(
    [
        dmc.TabsList(
            [
                dmc.TabsTab("asset1", value="asset1"),
                dmc.TabsTab("asset2", value="asset2"),
                dmc.TabsTab("asset3", value="asset3"),
            ]
        ),
        dmc.TabsPanel(dmc.Stack([
            ag_grid
        ]), value="asset1"),
        dmc.TabsPanel("Messages tab content", value="asset2"),
        dmc.TabsPanel("Settings tab content", value="asset3"),
    ],
    color="red", # default is blue
    orientation="vertical", # or "vertical"
    variant="default", # or "outline" or "pills"
    value="asset1"
)

app.layout = dmc.MantineProvider(
dmc.Modal(children=dmc.Tabs(
    [
        dmc.TabsList(
            [
                dmc.TabsTab("Gallery", value="gallery"),
                dmc.TabsTab("Messages", value="messages"),
                dmc.TabsTab("Settings", value="settings"),
            ]
        ),
        dmc.TabsPanel(dmc.Grid(children=[
            dmc.GridCol(span=12,children=popover),
            dmc.GridCol(span=12,children=asset_tab),
        ],align='flex-end'), value="gallery"),
        dmc.TabsPanel("Messages tab content", value="messages"),
        dmc.TabsPanel("Settings tab content", value="settings"),
    ],
    color="red", # default is blue
    orientation="horizontal", # or "vertical"
    variant="default", # or "outline" or "pills"
    value="gallery"
),centered=True,opened=True,size='xl')
)
@callback(
    Output("popover", "is_open"),
    [Input("toggle", "n_clicks")],
    [State("popover", "is_open")],
)
def toggle_popover(n, is_open):
    if n:
        return not is_open
    return is_open

@callback(
    [Output("grid", "columnDefs"), Output("grid", "rowData")],
    Input("my-button", "n_clicks"),
    State("column_populate", "value"),
    State("grid", "columnDefs"),
    State("grid", "rowData"),
)
def remove_column(n_clicks, column_name, input_columnDefs, row_data):
    if n_clicks is None or column_name is None:
        return no_update
    data = pd.DataFrame.from_records(row_data)
    data = data.drop(columns=list({column_name, "id"}.intersection(data.columns)))
    data.index.name = "id"
    print(data)
    return [
        col for col in input_columnDefs if col["field"] != column_name
    ], data.to_dict("records")


# app.clientside_callback(
#     """async function () {
#         var api = await dash_ag_grid.getApiAsync("grid")
#         api.redrawRows();
#         return dash_clientside.no_update
#     }""",
#     Output("grid", "id"),
#     Input("grid", "virtualRowData"),
# )

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

This is a MRE that I created - and it works perfectly as you expect. In my main app I have the same layout structure - but I see the flickering there. There is something else in play and I am hoping to get guidance on how I should proceed on debugging the problem?

Hi @sssaha1989

There can be some challenges rendering some content in tabs and modals which have have some hidden elements.

In the Modal, try setting In the dmc.Modal(keepMounted=True)

The callback to update the grid columns may be running before the grid is rendered properly. Try making the entire tab content with the grid updated in a callback,(rather than just tab children).

Also, try adding a minimum width to the grid container.

Thanks @AnnMarieW - this actually worked. I decided to update the code to render the whole grid and seems like that solved the problem.

Hi,

I have exactly the same issue, but I was able to debug it to a valueGetter in my columnDefs:

{“headerName”: “Overdue”, “valueGetter”: {“function”: “checkOverdue(params.data.due_date);”}},

Check ovverdue is defined as:

dagfuncs.checkOverdue = function (timestamp) {

return (timestamp < Date.now() / 1000)

};

The function still returns the correct values, however it constantly outputs the warning message in the console and causes also some issues with sorting. It worked fine before Dash 3.0.

1 Like