Custom Function as Dash Row Style

I have a very simple condition in my getrowstyle check function

getRowStyle={

    'styleConditions': [
        {
            'condition': 'params.data[`Utility Settings`].toLowerCase() === params.data[`DER Settings`].toLowerCase()',

            'style': {'color': 'green'},
        },
    ],
    'defaultStyle': {'color': 'red'},
},

However - the two columns being compared have a mixture of numeric and string values - therefore value 1 and 1.0 is treated different.

I was hoping to create a separate JS function like below:

dagcomponentfuncs.compareFunction = function (params) {
const numA = parseFloat(params.data[‘Utility Settings’]);
const numB = parseFloat(params.data[‘DER Settings’]);
// Check if both values can be converted to valid numbers
if (!isNaN(numA) && !isNaN(numB)) {
return numA === numB;
} else {
// Fallback to string comparison
return String(a).toLowerCase() === String(b).toLowerCase();
}
};

and use that as ‘condition’: “compareFunction(params)”,

This seems like does not work - I am pretty sure I am doing something stupid - what am I doing wrong?

Hello @sssaha1989

Make sure you are using the dashAgGridFunctions and not the components namespace.

Thanks @jinnyzor - that was the one - stupid mistake.

On a different note (most probably unrelated to this problem) - I am seeing this on the console

Is there something I can / should do? This is probably the cause of the flicker issue I was seeing before.

It’s always good to check that params.data exists before trying to use it.

Other than that, its hard to say without seeing more of your code.

What version of DAG are you running? And what version of Dash?

Dash 3.2 - Ag Grid 32.2.1

Warnings I see in console -

react-dom@18.v3_2_0m1754008766.3.1.js:23694 AG Grid: As of version 32.2.1, using rowSelection with the values “single” or “multiple” has been deprecated. Use the object value instead.

checkboxSelection is deprecated. Use rowSelection.checkboxes in GridOptions instead.

suppressRowClickSelection is deprecated. Use rowSelection.enableClickSelection instead.

I think I understand the errors - have we updated the documentation to reflect how we should update our existing code to match these?

That’s not the same issue with what your had in the previous.

But yes, they are working on. 32 docs, hopefully soon they can be released. :blush:

The original error trace

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
at Pl (AgGrid.react.js:131:20)
at Suspense ()
at h (AgGrid.react.js:16:31)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at dash_mantine_components.v2_1_0m1751907940.js:2:195229
at dash_mantine_components.v2_1_0m1751907940.js:2:742364
at km (dash_mantine_components.v2_1_0m1751907940.js:2:744642)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at dash_mantine_components.v2_1_0m1751907940.js:2:195229
at dash_mantine_components.v2_1_0m1751907940.js:2:826351
at Cg (dash_mantine_components.v2_1_0m1751907940.js:2:830207)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at dash_mantine_components.v2_1_0m1751907940.js:2:195229
at dash_mantine_components.v2_1_0m1751907940.js:2:246298
at dash_mantine_components.v2_1_0m1751907940.js:2:828598
at wg (dash_mantine_components.v2_1_0m1751907940.js:2:829626)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at dash_mantine_components.v2_1_0m1751907940.js:2:195229
at dash_mantine_components.v2_1_0m1751907940.js:2:558483
at dash_mantine_components.v2_1_0m1751907940.js:2:591235
at Bn (dash_mantine_components.v2_1_0m1751907940.js:2:561204)
at section ()
at dash_mantine_components.v2_1_0m1751907940.js:2:195229
at dash_mantine_components.v2_1_0m1751907940.js:2:559975
at h (dash_mantine_components.v2_1_0m1751907940.js:2:155330)
at div ()
at f (dash_mantine_components.v2_1_0m1751907940.js:2:152638)
at dash_mantine_components.v2_1_0m1751907940.js:2:560429
at dash_mantine_components.v2_1_0m1751907940.js:2:591830
at div ()
at dash_mantine_components.v2_1_0m1751907940.js:2:195229
at div ()
at dash_mantine_components.v2_1_0m1751907940.js:2:564330
at dash_mantine_components.v2_1_0m1751907940.js:2:246298
at dash_mantine_components.v2_1_0m1751907940.js:2:290475
at dash_mantine_components.v2_1_0m1751907940.js:2:291225
at dash_mantine_components.v2_1_0m1751907940.js:2:573549
at dash_mantine_components.v2_1_0m1751907940.js:2:246298
at dash_mantine_components.v2_1_0m1751907940.js:2:593506
at dash_mantine_components.v2_1_0m1751907940.js:2:595255
at za (dash_mantine_components.v2_1_0m1751907940.js:2:596052)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at div ()
at vn (Loading.react.js:71:5)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at f (dash_mantine_components.v2_1_0m1751907940.js:2:11391)
at mC (dash_mantine_components.v2_1_0m1751907940.js:2:969516)
at IC (dash_mantine_components.v2_1_0m1751907940.js:2:973587)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at Ae (Div.react.js:12:15)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at Ae (Div.react.js:12:15)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at dash_mantine_components.v2_1_0m1751907940.js:2:195229
at dash_mantine_components.v2_1_0m1751907940.js:2:742364
at km (dash_mantine_components.v2_1_0m1751907940.js:2:744642)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at div ()
at dash_mantine_components.v2_1_0m1751907940.js:2:195229
at dash_mantine_components.v2_1_0m1751907940.js:2:246298
at dash_mantine_components.v2_1_0m1751907940.js:2:743517
at Cm (dash_mantine_components.v2_1_0m1751907940.js:2:744436)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at f (dash_mantine_components.v2_1_0m1751907940.js:2:11391)
at mC (dash_mantine_components.v2_1_0m1751907940.js:2:969516)
at IC (dash_mantine_components.v2_1_0m1751907940.js:2:973587)
at CheckedComponent (CheckedComponent.tsx:5:13)
at DashContextProvider (DashContext.tsx:6:13)
at ComponentErrorBoundary (ComponentErrorBoundary.react.js:6:24)
at DashWrapper (DashWrapper.tsx:13:24)
at div ()
at div ()
at DebugMenu (DebugMenu.react.js:121:21)
at div ()
at UnconnectedGlobalErrorContainer (GlobalErrorContainer.react.js:7:24)
at ConnectFunction (connect.js:218:75)
at UnconnectedContainer (APIController.react.js:33:9)
at ConnectFunction (connect.js:218:75)
at UnconnectedAppContainer (AppContainer.react.js:13:24)
at ConnectFunction (connect.js:218:75)
at Provider (Provider.js:7:3)
at AppProvider (AppProvider.react.tsx:6:24)

My checkboxes are defined following the example
dagcomponentfuncs.Checkbox = function (props) { const {setData, data} = props; function onClick() { if (!('checked' in event.target)) { const checked = !event.target.children[0].checked; const colId = props.column.colId; props.node.setDataValue(colId, checked); } } function checkedHandler() { // update grid data const checked = event.target.checked; const colId = props.column.colId; props.node.setDataValue(colId, checked); // update cellRendererData prop so it can be used to trigger a callback setData(checked); } return React.createElement( 'div', {onClick: onClick}, React.createElement('input', { type: 'checkbox', checked: props.value, onChange: checkedHandler, style: {cursor: 'pointer'}, }) ); };

It’s hard to tell what is happening.

can you please make a small example app for us to test?

So I created one - and you may find this weird design - but this is how I did in my main application (maybe was not a smart choice and I am going to update this as soon as possible)

`set_props is the culprit

import dash
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", "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)
    dash.set_props("grid",{"columnDefs":[
        col for col in input_columnDefs if col["field"] != column_name
    ],"rowData": data.to_dict("records")})
    # return data.to_dict("records")
    # 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)

After running the app - click on the toggle button and select a column name and then click remove - check the console and it hits that maximum depth issue.

Ok, I see the issue with the maximum call stack. here is the smallest reproducible:

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

app = Dash(__name__)

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.Stack(
    [
        dmc.Select(
        value=None,
        data=[x["field"] for x in columnDefs],
        id="column_populate",
        clearable=True,
        # comboboxProps=dict(withInPortal=True),
    ),
    dmc.Button("Remove Column", id="my-button", color="blue",fullWidth=True),
    ]
)

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

app.layout = dmc.MantineProvider(
    html.Div([
        dmc.Grid(children=[
            dmc.GridCol(span=12,children=popover),
            dmc.GridCol(span=12,children=ag_grid),
        ],align='flex-end')
    ],style={'height':'100vh','width':'100vw','padding':'20px'})
)

@callback(
    Output("grid", "columnDefs"),
    Input("my-button", "n_clicks"),
    State("column_populate", "value"),
    State("grid", "columnDefs"),
)
def remove_column(n_clicks, column_name, input_columnDefs):
    if n_clicks is None or column_name is None:
        return no_update
    return [
        col for col in input_columnDefs if col["field"] != column_name
    ]

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

Awesome - do I need to post this on the GITHUB issues for Ag Grid? Or as you are already aware - I am assuming you will somehow solve this problem for all of us :slight_smile:

It’s only an issue in development, if you dont run debug=True, then it goes away. I have a fix for it pending review. :slight_smile: