serverSide rowModelType with Storing Changes

Haha, whoops.

Depends on the amount of data that you build.

Try this version, its similar to yours, except that it stores the changes on the server using your session as a key:

app.py

import dash_ag_grid as dag
from dash import Dash, Input, Output, html, dcc, State, no_update
import requests, json
import flask
import pandas as pd
import uuid

app = Dash(__name__)

server = app.server
server.secret_key = "xyz"

alterations = {}

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)
df.reset_index(inplace=True)
df.rename(columns={"index": "InputRowId"}, inplace=True)

rowData = df.to_dict('records')

columnDefs = [
    # Row group by country and by year is enabled.
    {"field": "country", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "year", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "athlete", "sortable": True, "filter": True},
    {"field": "age", "sortable": True, "filter": True},
    {"field": "date", "sortable": True, "filter": True},
    {"field": "sport", "sortable": True, "filter": True, 'editable': True},
    {"field": "total", "sortable": True, "filter": True, "aggFunc": "sum"},
    {"field": "InputRowId", "sortable": True, "filter": True, 'hide': True, 'suppressColumnsToolPanel': True},
]


def extractRowsFromData(request, df):
    response = []

    ## create session uid if not already done
    if not flask.session.get('uid'):
        flask.session['uid'] = uuid.uuid4()

    ## pull session stored changes
    if not alterations.get(flask.session['uid']):
        alterations[flask.session['uid']] = {}
    myalterations = alterations.get(flask.session['uid'])

    groupBy = []
    dff = df.copy()
    for k in myalterations.keys():
        for key in myalterations[k].keys():
            dff.at[k, key] = myalterations[k][key]

    if request['rowGroupCols']:
        groupBy = [i['id'] for i in request['rowGroupCols']]
    agg = {}
    if request['valueCols']:
        agg = {i['id']: i['aggFunc'] for i in request['valueCols']}
    if not request['groupKeys']:
        if groupBy:
            if agg:
                dff = dff.groupby(groupBy[0]).agg(agg).reset_index()
            else:
                dff = dff.groupby(groupBy[0]).agg('count').reset_index()
    else:
        for i in range(len(request['groupKeys'])):
            dff = dff[dff[request['rowGroupCols'][i]['id']] == request['groupKeys'][i]]
        if len(request['groupKeys']) != len(groupBy):
            if agg:
                dff = dff.groupby(groupBy[:len(request['groupKeys']) + 1]).agg(agg).reset_index()
            else:
                dff = dff.groupby(groupBy[:len(request['groupKeys']) + 1]).agg('count').reset_index()
    dff = dff.sort_values(by=[i['colId'] for i in request['sortModel'] if i['colId'] in dff.columns],
                          ascending=[i['sort'] == 'asc' for i in request['sortModel'] if i['colId'] in dff.columns])

    return {'rowData': dff.to_dict('records')[request['startRow']: request['endRow']], 'rowCount': len(dff)}


@server.route('/api/serverData', methods=['POST'])
def serverData():
    response = extractRowsFromData(flask.request.json, df)
    return json.dumps(response)


grid = html.Div(
    [
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            dashGridOptions={"rowSelection": "multiple", "sideBar": True},
            defaultColDef=dict(
                resizable=True,
                enableRowGroup=True,
                enableValue=True,
                enablePivot=True
            ),
            enableEnterpriseModules=True,
            rowModelType="serverSide",
            style={'overflow': 'auto', 'resize': 'both'}
        ),
    ]
)

app.layout = html.Div(
    [
        dcc.Markdown("Example: Organisational Hierarchy using Tree Data "),
        grid,
        html.Div(id='hidden-div', style={'display': 'hidden'}),
    dcc.Store(id='stored_changes', data={}, storage_type='local')
    ]
)

app.clientside_callback(
    """async function (id) {
        const updateData = (grid) => {
          var datasource = createServerSideDatasource();
          grid.setServerSideDatasource(datasource);
        };
        var grid;
        grid = await window.dash_ag_grid.getApiAsync(id)
        if (grid) {
            updateData(grid)
        }
        return window.dash_clientside.no_update
    }""",
    Output('grid', 'id'), Input('grid', 'id')
)

@app.callback(Output('stored_changes', 'data', allow_duplicate=True),
              Input('grid', 'cellValueChanged'),
              prevent_initial_call=True)
def syncData(changes):
    ## load and update alterations
    if not alterations.get(flask.session['uid']):
        alterations[flask.session['uid']] = {}
    myalterations = alterations.get(flask.session['uid'])
    for c in changes:
        if not myalterations.get(c['data']['InputRowId']):
            myalterations[c['data']['InputRowId']] = {}
        myalterations[c['data']['InputRowId']][c['colId']] = c['value']
    return no_update

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

js

async function getServerData(request) {
    response = await fetch('./api/serverData', {'method': 'POST', 'body': JSON.stringify(request),
      'headers': {'content-type': 'application/json'}})
    return response.json()
}

function createServerSideDatasource() {
  const dataSource = {
    getRows: async (params) => {
      var result = await getServerData(params.request)
      setTimeout(function () {
        params.success(result);
      }, 200);
    },
  };
  return dataSource;
}

I added a couple of fixes, your version would have only stored one column per key, this one will store all the changes on the df.

I also am making sure that the sorting columns exist in the data, as this was causing some error.