Cannot read properties of undefined (reading 'Spinner')

Hi. Help to fix error Cannot read properties of undefined (reading 'Spinner'). I want to reproduce logic of https://dash.plotly.com/dash-ag-grid/infinite-row-model#specify-selectable-rows with spinner. Example 2: Equal Pagination Page Size and Large Infinite Block Size.

Hello @AlesiaSel,

Without seeing your code, its hard to tell where your code is going wrong.

My guess is that you dont have the dbc importing into your app.

#dag-docs

@Skiks

@jinnyzor I tried to run code from dash

import dash_ag_grid as dag
from dash import Dash, Input, Output, html, no_update, callback
import pandas as pd
import time

app = Dash(__name__)

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    # this row shows the row index, doesn't use any data from the row
    {
        "headerName": "ID",
        "maxWidth": 100,
        # it is important to have node.id here, so that when the id changes (which happens
        # when the row is loaded) then the cell is refreshed.
        "valueGetter": {"function": "params.node.id"},
        "cellRenderer": "SpinnerCellRenderer",
    },
    {"field": "athlete", "minWidth": 150},
    {"field": "country", "minWidth": 150},
    {"field": "year"},
    {"field": "sport", "minWidth": 150},
    {"field": "total"},
]

defaultColDef = {
    "flex": 1,
    "minWidth": 150,
    "sortable": False,
    "resizable": True,
}

app.layout = html.Div(
    [
        dag.AgGrid(
            id="infinite-row-model-pagination",
            columnDefs=columnDefs,
            defaultColDef=defaultColDef,
            rowModelType="infinite",
            dashGridOptions={
                # The number of rows rendered outside the viewable area the grid renders.
                "rowBuffer": 0,
                # How many blocks to keep in the store. Default is no limit, so every requested block is kept.
                "maxBlocksInCache": 2,
                "cacheBlockSize": 100,
                "cacheOverflowSize": 2,
                "maxConcurrentDatasourceRequests": 2,
                "infiniteInitialRowCount": 1,
                "rowSelection": "multiple",
                "pagination": True,
            },
        ),
    ],
)


@callback(
    Output("infinite-row-model-pagination", "getRowsResponse"),
    Input("infinite-row-model-pagination", "getRowsRequest"),
)
def infinite_scroll(request):
    # simulate slow callback
    time.sleep(2)

    if request is None:
        return no_update
    partial = df.iloc[request["startRow"]: request["endRow"]]
    return {"rowData": partial.to_dict("records"), "rowCount": len(df.index)}


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

https://dash.plotly.com/dash-ag-grid/infinite-row-model?_gl=1hxn8mq_gaMTIxMTkxNzA0Ni4xNzAxNDMyOTAz_ga_6G7EE0JNSC*MTcxMjgzOTk2MC4xMDEuMS4xNzEyODQ0NzQ0LjYwLjAuMA…#specify-selectable-rows

Example 2: Equal Pagination Page Size and Large Infinite Block Size

here js file too

structure of folders:
-assets/
----css/
----js/
--------this js file
-app.py

I’m not very good at js, so I need help.

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

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    # this row shows the row index, doesn't use any data from the row
    {
        "headerName": "ID",
        "maxWidth": 100,
        # it is important to have node.id here, so that when the id changes (which happens
        # when the row is loaded) then the cell is refreshed.
        "valueGetter": {"function": "params.node.id"},
        "cellRenderer": "SpinnerCellRenderer",
    },
    {"field": "athlete", "minWidth": 150},
    {"field": "country", "minWidth": 150},
    {"field": "year"},
    {"field": "sport", "minWidth": 150},
    {"field": "total"},
]

defaultColDef = {
    "flex": 1,
    "minWidth": 150,
    "sortable": False,
    "resizable": True,
}

app.layout = html.Div(
    [
        dag.AgGrid(
            id="infinite-row-model-pagination",
            columnDefs=columnDefs,
            defaultColDef=defaultColDef,
            rowModelType="infinite",
            dashGridOptions={
                # The number of rows rendered outside the viewable area the grid renders.
                "rowBuffer": 0,
                # How many blocks to keep in the store. Default is no limit, so every requested block is kept.
                "maxBlocksInCache": 2,
                "cacheBlockSize": 100,
                "cacheOverflowSize": 2,
                "maxConcurrentDatasourceRequests": 2,
                "infiniteInitialRowCount": 1,
                "rowSelection": "multiple",
                "pagination": True,
            },
        ),
    ],
)


@callback(
    Output("infinite-row-model-pagination", "getRowsResponse"),
    Input("infinite-row-model-pagination", "getRowsRequest"),
)
def infinite_scroll(request):
    # simulate slow callback
    time.sleep(2)

    if request is None:
        return no_update
    partial = df.iloc[request["startRow"]: request["endRow"]]
    return {"rowData": partial.to_dict("records"), "rowCount": len(df.index)}


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

Hi @AlesiaSel

Thanks for asking the question! I’ll do a PR to fix the docs.

Even if you don’t use dash_bootstrap_components anywhere else in your app, it’s necessary for the SpinnerCellRenderer component , so it’s necessary to add:

import dash_bootstrap_components as dbc

and to add the Bootstrap stylesheet in the app constructor:

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
1 Like

i ran with this but it does not work for me


why?

Be sure that you are importing dash_bootstrap_components as well.

i copy-paste your code and run

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

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    # this row shows the row index, doesn't use any data from the row
    {
        "headerName": "ID",
        "maxWidth": 100,
        # it is important to have node.id here, so that when the id changes (which happens
        # when the row is loaded) then the cell is refreshed.
        "valueGetter": {"function": "params.node.id"},
        "cellRenderer": "SpinnerCellRenderer",
    },
    {"field": "athlete", "minWidth": 150},
    {"field": "country", "minWidth": 150},
    {"field": "year"},
    {"field": "sport", "minWidth": 150},
    {"field": "total"},
]

defaultColDef = {
    "flex": 1,
    "minWidth": 150,
    "sortable": False,
    "resizable": True,
}

app.layout = html.Div(
    [
        dag.AgGrid(
            id="infinite-row-model-pagination",
            columnDefs=columnDefs,
            defaultColDef=defaultColDef,
            rowModelType="infinite",
            dashGridOptions={
                # The number of rows rendered outside the viewable area the grid renders.
                "rowBuffer": 0,
                # How many blocks to keep in the store. Default is no limit, so every requested block is kept.
                "maxBlocksInCache": 2,
                "cacheBlockSize": 100,
                "cacheOverflowSize": 2,
                "maxConcurrentDatasourceRequests": 2,
                "infiniteInitialRowCount": 1,
                "rowSelection": "multiple",
                "pagination": True,
            },
        ),
    ],
)


@callback(
    Output("infinite-row-model-pagination", "getRowsResponse"),
    Input("infinite-row-model-pagination", "getRowsRequest"),
)
def infinite_scroll(request):
    # simulate slow callback
    time.sleep(2)

    if request is None:
        return no_update
    partial = df.iloc[request["startRow"]: request["endRow"]]
    return {"rowData": partial.to_dict("records"), "rowCount": len(df.index)}


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

assets/js/dashAgGridComponentFunctions.js:

its not work for me.

does it work for you?

Ooooohhhhh.

Upgrade your dash version to 2.16.1.

There was an issue with importing unused libraries into the apps window initially.

Also, as another note, we are on ag grid v31 now.

1 Like

Hi. Yep. It works with dash==2.16.1, not dash==2.16.0. But when the page is initially loaded, I encounter the following
image
which was not the case in 2.16.0

is it possible to fix it somehow?
Thanks

This is something to do with hot reloading, you should be able to ignore for now. :grin:

Please, help me figure out 1 more point. this is a minimal reproducible example of my app in which I use a spinner

import dash
from dash import dcc, html, ctx, no_update, callback, Input, Output, State
import pandas as pd
import dash_mantine_components as dmc
import dash_ag_grid as dag
from dash.exceptions import PreventUpdate
import plotly.express as px
import dash_bootstrap_components as dbc

df = px.data.gapminder()

data = {'df': df}

external_stylesheets=[dbc.themes.BOOTSTRAP]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

COLUMNS = ['ID', 'country', 'continent', 'year', 'lifeExp', 'pop', 'gdpPercap', 'iso_alpha', 'iso_num']
def generate_default_col_defs(columns=COLUMNS):
    columnDefs=[
        {'headerName': col, 'field': col, 'minWidth': 150, 'maxWidth': 150, 'width': 150} for col in columns
    ]
    return columnDefs


def generate_col_defs(columns=COLUMNS):
    columnDefs=[
        {'headerName': col, 'field': col, 'minWidth': 150, 'maxWidth': 150, 'width': 150,
         "valueFormatter": {"function": "params.value ? (params.value.toFixed(2)) : null"}} if col=='lifeExp'
        else {"headerName": col, 'field': col, 'minWidth': 100, 'maxWidth': 100, 'width': 100, 'pinned': 'left', 
              "valueGetter": {"function": "params.node.id"}, "cellRenderer": "SpinnerCellRenderer",
              }
        if col == 'ID'
        else {'headerName': col, 'field': col, 'minWidth': 150, 'maxWidth': 150, 'width': 150} for col in columns
    ]
    return columnDefs


app.layout = dmc.MantineProvider(
    children=html.Div(
        children=[
            dmc.Group(
                children=[
                    dcc.Dropdown(
                        id='dropdown',
                        options=[
                            {'label': 'data1.csv', 'value': 'df'},
                        ],
                        placeholder='Choose CSV'
                    ),
                    dmc.Button('Load data', id='load-button', n_clicks=0),
                ],
            ),
            html.Div(
                children=[
                    dag.AgGrid(
                        id='infinite-grid',
                        rowModelType="infinite",
                        columnDefs=generate_default_col_defs(),
                        columnSize="autoSize",
                        defaultColDef={'resizable': True, 'editable': False, 'sortable': True},
                        dashGridOptions={"rowBuffer": 0,
                                         "maxBlocksInCache": 2,
                                         "cacheBlockSize": 100,
                                         "cacheOverflowSize": 2,
                                         "maxConcurrentDatasourceRequests": 2,
                                         "infiniteInitialRowCount": 1,
                                         "pagination": True},
                    ),
                ],
            ),
        ],
    ),
)


@callback(
    output=[
        Output('infinite-grid', "getRowsResponse"),
        Output('infinite-grid', 'paginationGoTo'),
        Output('infinite-grid', "columnDefs"),
    ],
    inputs=[
        Input('load-button', "n_clicks"),
        Input('infinite-grid', "getRowsRequest"),
    ],
    state=[
        State('dropdown', "value"),
    ],
    running=[
        (Output('load-button', "disabled"), True, False),
    ],
    # background=True,
    prevent_initial_call=True
)
def update_layout(n_clicks, request, value):
    if value:
        dff = data.get(value).copy()
        dff.insert(0, 'ID', dff.index)
        if 'load-button' == ctx.triggered_id:
            partial = dff.to_dict('records')[0:100]
            response = {"rowData": partial, "rowCount": len(dff)}
            return response, 0, generate_col_defs()
        elif request:
            partial = dff.to_dict('records')[request["startRow"]:request["endRow"]]
            response = {"rowData": partial, "rowCount": len(dff)}
            return response, no_update, generate_col_defs()
    raise PreventUpdate

if __name__ == '__main__':
    app.run_server(debug=True)

I need this realization.
When the data is initially loaded into the grid - spinner, it does not work. Only when switching between pages. Why is this happening and what needs to be changed so that the spinner appears when the button is pressed. Thanks

Here is a version that should work for what you want to do:

import dash
from dash import dcc, html, ctx, no_update, callback, Input, Output, State
import pandas as pd
import dash_mantine_components as dmc
import dash_ag_grid as dag
from dash.exceptions import PreventUpdate
import plotly.express as px
import dash_bootstrap_components as dbc
import time

df = px.data.gapminder()

data = {'df': df}

external_stylesheets=[dbc.themes.BOOTSTRAP]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

COLUMNS = ['ID', 'country', 'continent', 'year', 'lifeExp', 'pop', 'gdpPercap', 'iso_alpha', 'iso_num']
def generate_default_col_defs(columns=COLUMNS):
    columnDefs=[
        {'field': col} for col in columns
    ]
    return columnDefs


def generate_col_defs(columns=COLUMNS):
    columnDefs=[
        {'field': col,
         "valueFormatter": {"function": "params.value ? (params.value.toFixed(2)) : null"}} if col=='lifeExp'
        else {'field': col, 'pinned': 'left',
              "valueGetter": {"function": "params.node.id"}, "cellRenderer": "SpinnerCellRenderer",
              }
        if col == 'ID'
        else {'field': col} for col in columns
    ]
    return columnDefs


app.layout = dmc.MantineProvider(
    children=html.Div(
        children=[
            dmc.Group(
                children=[
                    dcc.Dropdown(
                        id='dropdown',
                        options=[
                            {'label': 'data1.csv', 'value': 'df'},
                        ],
                        placeholder='Choose CSV'
                    ),
                    dmc.Button('Load data', id='load-button', n_clicks=0),
                ],
            ),
            html.Div(
                children=[
                    dag.AgGrid(
                        id='infinite-grid',
                        rowModelType="infinite",
                        columnDefs=generate_default_col_defs(),
                        columnSize="autoSize",
                        defaultColDef={'resizable': True, 'editable': False, 'sortable': True,
                                       'minWidth': 150, 'maxWidth': 150, 'width': 150},
                        dashGridOptions={"rowBuffer": 0,
                                         "maxBlocksInCache": 2,
                                         "cacheBlockSize": 100,
                                         "cacheOverflowSize": 2,
                                         "maxConcurrentDatasourceRequests": 2,
                                         "infiniteInitialRowCount": 1,
                                         "pagination": True},
                    ),
                ],
            ),
        ],
    ),
)


@callback(
    output=[
        Output('infinite-grid', "getRowsResponse"),
        Output('infinite-grid', 'paginationGoTo'),
        Output('infinite-grid', "columnDefs"),
    ],
    inputs=[
        Input('load-button', "n_clicks"),
        Input('infinite-grid', "getRowsRequest"),
    ],
    state=[
        State('dropdown', "value"),
    ],
    running=[
        (Output('load-button', "disabled"), True, False),
    ],
    # background=True,
    prevent_initial_call=True
)
def update_layout(n_clicks, request, value):
    if value:
        # time.sleep(1)
        dff = data.get(value).copy()
        dff.insert(0, 'ID', dff.index)
        if 'load-button' == ctx.triggered_id:
            partial = dff.to_dict('records')[0:100]
            response = {"rowData": partial, "rowCount": len(dff)}
            return response, 0, generate_col_defs()
        elif request:
            partial = dff.to_dict('records')[request["startRow"]:request["endRow"]]
            response = {"rowData": partial, "rowCount": len(dff)}
            return response, no_update, generate_col_defs()
    raise PreventUpdate

app.clientside_callback(
    """
        (n, id) => {
            if (n) {
                grid = dash_ag_grid.getApi(id)
                grid.showLoadingOverlay()
            }
            return window.dash_clientside.no_update
        }
    """,
    Output('infinite-grid', "id"),
        Input('load-button', "n_clicks"),
        State('infinite-grid', 'id'),
    prevent_initial_call=True
)

app.clientside_callback(
    """
        (n, id) => {
            grid = dash_ag_grid.getApi(id)
            grid.hideOverlay()
            return window.dash_clientside.no_update
        }
    """,
    Output('infinite-grid', "id", allow_duplicate=True),
        Input('infinite-grid', "getRowsResponse"),
        State('infinite-grid', 'id'),
    prevent_initial_call=True
)

if __name__ == '__main__':
    app.run_server(debug=True)

The loading spinner only works when loading the new data, which technically when you first load the grid you have data showing. It also doesnt have any placeholders for the data and is cached, so there isnt a new grid call for the rowRequest.

Thanks much!!! Working good)))) :innocent: :star_struck:
if first grid loading:
image
loading for next page:
image

1 Like