Repository: plotly/dash-ag-grid (language: python)

Issue:
JSON Parse error when using dash_ag_grid and dash pages
```
dash                           2.16.1
dash_ag_grid                   31.0.1
dash-bootstrap-components      1.5.0
dash-core-components           2.0.0
dash-html-components           2.0.0
dash-mantine-components        0.12.1
dash-table                     5.0.0
dash-testing-stub              0.0.2

```

**Description**

Set up two dash pages. One should contain `dash_ag_grid`.
Run the app, open the page with `dash_ag_grid` and quickly (in 1 second) switch to different page.
Next error occurs in browser console:
```
SyntaxError: JSON Parse error: Unexpected identifier "undefined"
```

**Expected behavior**

No console error occurred

**Screenshots**

<img width="492" alt="image" src="https://github.com/plotly/dash/assets/35569332/926522cc-ce5e-4f0c-a82e-fa13bb9e31ce">




Discussion / hints:
Hi @l0uden 
Thank you for reporting the issue. I was able to reproduce the error. This appears to be a problem with [dash-ag-grid](https://github.com/plotly/dash-ag-grid) and will likely be transferred there for further investigation.

I also noticed this error message:

>ag-grid-community.auto.esm.js:438 AG Grid: Grid API function getColumnState() cannot be called as the grid has been destroyed.  It is recommended to remove local references to the grid api. Alternatively, check gridApi.isDestroyed() to avoid calling methods against a destroyed grid.  To run logic when the grid is about to be destroyed use the gridPreDestroy event. See: See https://www.ag-grid.com/react-data-grid/grid-lifecycle/#grid-pre-destroyed value @ ag-grid-community.auto.esm.js:438


Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa819ac..00e79d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,8 @@ Links "DE#nnn" prior to version 2.0 point to the Dash Enterprise closed-source D
 
 ## unreleased
 ### Fixed
- - [#325](https://github.com/plotly/dash-ag-grid/pull/325).  Fixes issue #324 where `pivotComparator` functions were not sorting columns correctly because they were only being passed `params`.
+ - [#300](https://github.com/plotly/dash-ag-grid/pull/300) Fixes issue [#299](https://github.com/plotly/dash-ag-grid/pull/299) where grid was unmounted and trying to update the `columnState`.
+ - [#325](https://github.com/plotly/dash-ag-grid/pull/325)  Fixes issue [#324](https://github.com/plotly/dash-ag-grid/pull/324) where `pivotComparator` functions were not sorting columns correctly because they were only being passed `params`.
  - [#314](https://github.com/plotly/dash-ag-grid/pull/314)
    - locking selenium for tests that were failing due to missing import
  - [#313](https://github.com/plotly/dash-ag-grid/pull/313)
diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js
index 52a2b92..57353de 100644
--- a/src/lib/fragments/AgGrid.react.js
+++ b/src/lib/fragments/AgGrid.react.js
@@ -554,9 +554,11 @@ export default class DashAgGrid extends Component {
         if (rowModelType === 'clientSide') {
             propsToSet.virtualRowData = this.virtualRowData();
         }
-        propsToSet.columnState = JSON.parse(
-            JSON.stringify(this.state.gridApi.getColumnState())
-        );
+        if (!this.state.gridApi.isDestroyed()) {
+            propsToSet.columnState = JSON.parse(
+                JSON.stringify(this.state.gridApi.getColumnState())
+            );
+        }
         setProps(propsToSet);
     }
 
@@ -570,6 +572,7 @@ export default class DashAgGrid extends Component {
 
     componentWillUnmount() {
         this.setState({mounted: false, gridApi: null});
+        this.props.setProps = () => {};
         if (this.props.id) {
             delete agGridRefs[this.props.id];
             eventBus.remove(this.props.id);
@@ -1308,15 +1311,20 @@ export default class DashAgGrid extends Component {
         if (!this.state.gridApi || !this.state.mounted) {
             return;
         }
+        if (!this.state.gridApi.isDestroyed()) {
+            var columnState = JSON.parse(
+                JSON.stringify(this.state.gridApi.getColumnState())
+            );
 
-        var columnState = JSON.parse(
-            JSON.stringify(this.state.gridApi.getColumnState())
-        );
-
-        this.props.setProps({
-            columnState,
-            updateColumnState: false,
-        });
+            this.props.setProps({
+                columnState,
+                updateColumnState: false,
+            });
+        } else {
+            this.props.setProps({
+                updateColumnState: false,
+            });
+        }
     }
 
     buildArray(arr1, arr2) {


Test patch:
diff --git a/tests/test_cell_data_type_override.py b/tests/test_cell_data_type_override.py
index c1031ce..f4bdd54 100644
--- a/tests/test_cell_data_type_override.py
+++ b/tests/test_cell_data_type_override.py
@@ -4,7 +4,6 @@
 from dash import Dash, html
 from . import utils
 
-
 def test_cd001_cell_data_types_override(dash_duo):
     app = Dash(__name__)
 
diff --git a/tests/test_column_state.py b/tests/test_column_state.py
index 5294d53..c771d6d 100644
--- a/tests/test_column_state.py
+++ b/tests/test_column_state.py
@@ -11,123 +11,125 @@
 df = px.data.election()
 default_display_cols = ["district_id", "district", "winner"]
 
-
-def test_cs001_column_state(dash_duo):
-    app = Dash(__name__)
-    columnDefs = [
+columnDefs = [
         {"headerName": "Make", "field": "make"},
         {"headerName": "Model", "field": "model"},
         {"headerName": "Price", "field": "price"},
     ]
 
-    alt_columnDefs = [
-        {"field": "price", "pinned": False, "sort": "asc"},
-        {"field": "model", "pinned": False},
-        {"field": "make", "pinned": False},
-    ]
+alt_columnDefs = [
+    {"field": "price", "pinned": False, "sort": "asc"},
+    {"field": "model", "pinned": False},
+    {"field": "make", "pinned": False},
+]
 
-    defaultColDef = {
-        "initialWidth": 150,
-        "sortable": True,
-        "resizable": True,
-        "filter": True,
-    }
-
-    rowData = [
-        {"make": "Toyota", "model": "Celica", "price": 35000},
-        {"make": "Ford", "model": "Mondeo", "price": 32000},
-        {"make": "Porsche", "model": "Boxster", "price": 72000},
-    ]
+defaultColDef = {
+    "initialWidth": 150,
+    "sortable": True,
+    "resizable": True,
+    "filter": True,
+}
 
-    colState = [
-        {
-            "colId": "make",
-            "width": 150,
-            "hide": False,
-            "pinned": "left",
-            "sort": None,
-            "sortIndex": None,
-            "aggFunc": None,
-            "rowGroup": False,
-            "rowGroupIndex": None,
-            "pivot": False,
-            "pivotIndex": None,
-            "flex": None,
-        },
-        {
-            "colId": "price",
-            "width": 150,
-            "hide": False,
-            "pinned": "left",
-            "sort": None,
-            "sortIndex": None,
-            "aggFunc": None,
-            "rowGroup": False,
-            "rowGroupIndex": None,
-            "pivot": False,
-            "pivotIndex": None,
-            "flex": None,
-        },
-        {
-            "colId": "model",
-            "width": 150,
-            "hide": False,
-            "pinned": None,
-            "sort": None,
-            "sortIndex": None,
-            "aggFunc": None,
-            "rowGroup": False,
-            "rowGroupIndex": None,
-            "pivot": False,
-            "pivotIndex": None,
-            "flex": None,
-        },
-    ]
+rowData = [
+    {"make": "Toyota", "model": "Celica", "price": 35000},
+    {"make": "Ford", "model": "Mondeo", "price": 32000},
+    {"make": "Porsche", "model": "Boxster", "price": 72000},
+]
+
+colState = [
+    {
+        "colId": "make",
+        "width": 150,
+        "hide": False,
+        "pinned": "left",
+        "sort": None,
+        "sortIndex": None,
+        "aggFunc": None,
+        "rowGroup": False,
+        "rowGroupIndex": None,
+        "pivot": False,
+        "pivotIndex": None,
+        "flex": None,
+    },
+    {
+        "colId": "price",
+        "width": 150,
+        "hide": False,
+        "pinned": "left",
+        "sort": None,
+        "sortIndex": None,
+        "aggFunc": None,
+        "rowGroup": False,
+        "rowGroupIndex": None,
+        "pivot": False,
+        "pivotIndex": None,
+        "flex": None,
+    },
+    {
+        "colId": "model",
+        "width": 150,
+        "hide": False,
+        "pinned": None,
+        "sort": None,
+        "sortIndex": None,
+        "aggFunc": None,
+        "rowGroup": False,
+        "rowGroupIndex": None,
+        "pivot": False,
+        "pivotIndex": None,
+        "flex": None,
+    },
+]
+
+alt_colState = [
+    {
+        "colId": "price",
+        "width": 198,
+        "hide": False,
+        "pinned": None,
+        "sort": "asc",
+        "sortIndex": None,
+        "aggFunc": None,
+        "rowGroup": False,
+        "rowGroupIndex": None,
+        "pivot": False,
+        "pivotIndex": None,
+        "flex": None,
+    },
+    {
+        "colId": "model",
+        "width": 150,
+        "hide": False,
+        "pinned": None,
+        "sort": None,
+        "sortIndex": None,
+        "aggFunc": None,
+        "rowGroup": False,
+        "rowGroupIndex": None,
+        "pivot": False,
+        "pivotIndex": None,
+        "flex": None,
+    },
+    {
+        "colId": "make",
+        "width": 150,
+        "hide": False,
+        "pinned": None,
+        "sort": None,
+        "sortIndex": None,
+        "aggFunc": None,
+        "rowGroup": False,
+        "rowGroupIndex": None,
+        "pivot": False,
+        "pivotIndex": None,
+        "flex": None,
+    },
+]
+
+
+def test_cs001_column_state(dash_duo):
+    app = Dash(__name__)
 
-    alt_colState = [
-        {
-            "colId": "price",
-            "width": 198,
-            "hide": False,
-            "pinned": None,
-            "sort": "asc",
-            "sortIndex": None,
-            "aggFunc": None,
-            "rowGroup": False,
-            "rowGroupIndex": None,
-            "pivot": False,
-            "pivotIndex": None,
-            "flex": None,
-        },
-        {
-            "colId": "model",
-            "width": 150,
-            "hide": False,
-            "pinned": None,
-            "sort": None,
-            "sortIndex": None,
-            "aggFunc": None,
-            "rowGroup": False,
-            "rowGroupIndex": None,
-            "pivot": False,
-            "pivotIndex": None,
-            "flex": None,
-        },
-        {
-            "colId": "make",
-            "width": 150,
-            "hide": False,
-            "pinned": None,
-            "sort": None,
-            "sortIndex": None,
-            "aggFunc": None,
-            "rowGroup": False,
-            "rowGroupIndex": None,
-            "pivot": False,
-            "pivotIndex": None,
-            "flex": None,
-        },
-    ]
 
     app.layout = html.Div(
         [
@@ -262,3 +264,52 @@ def loadState(n):
         timeout=3,
     )
     grid.wait_for_all_header_texts(["Make", "Price", "Model"])
+
+def test_cs002_column_state(dash_duo):
+    app = Dash(__name__)
+
+    app.layout = html.Div(
+        [
+            html.Div(
+                [
+                    html.Button(
+                        "Add Grid", id="add-grid", n_clicks=0
+                    ),
+                    html.Div(
+                        id='grid-holder'
+                    )
+                ],
+            ),
+        ]
+    )
+
+    app.clientside_callback(
+        """async ()=> {
+            await new Promise(resolve => setTimeout(resolve, 400));
+            return []
+        }""",
+        Output('grid-holder', 'children', allow_duplicate=True),
+        Input('grid-holder', 'children'),
+        prevent_initial_call=True
+    )
+
+    @app.callback(
+        Output('grid-holder', 'children'),
+        Input('add-grid', 'n_clicks'),
+        prevent_initial_call=True
+    )
+    def make_grid(n):
+        return dag.AgGrid(
+                id=f"grid_{n}",
+                columnDefs=columnDefs,
+                defaultColDef=defaultColDef,
+                rowData=rowData,
+                columnSize='Auto'
+            )
+
+    dash_duo.start_server(app)
+
+    for x in range(10):
+        dash_duo.find_element("#add-grid").click()
+        time.sleep(2)  # pausing to emulate separation because user inputs
+    assert list(filter(lambda i: i.get("level") != "WARNING", dash_duo.get_logs())) == []
\ No newline at end of file