2025, Dec 21 07:00

Fixing undefined(undefined) in Streamlit st-aggrid agRichSelectCellEditor multiSelect with consistent string values

Fix undefined(undefined) previews in st-aggrid agRichSelectCellEditor multiSelect: use string values, align parser and formatter, and render Pink;Purple cells.

When you wire up agRichSelectCellEditor with multiSelect in st-aggrid and feed it objects, the in-editor preview can suddenly show undefined(undefined) while you are picking values. The data ends up correct after selection, but the UX during editing is confusing. The goal is straightforward: keep the dropdown showing both name and code, and have the cell render a clean Pink;Purple string once the edit is committed.

Reproducing the issue

The following minimal setup demonstrates the behavior. Column c uses the rich select editor with multiSelect. Choices are dictionaries containing name and code, the preview is formatted as name (code), and the saved value becomes a semicolon-joined list of names. During selection, however, the preview shows undefined(undefined).

import streamlit as st
import pandas as pd

from st_aggrid import AgGrid, GridOptionsBuilder, JsCode

grid_df = pd.DataFrame(
    "",
    index=range(10),
    columns=list("c"),
)

cfg = GridOptionsBuilder.from_dataframe(grid_df)
cfg.configure_default_column(editable=True)

palette = [
    { "name": "Pink", "code": "#FFC0CB" },
    { "name": "Purple", "code": "#A020F0" },
    { "name": "Blue", "code": "#0000FF" },
    { "name": "Green", "code": "#008000" },
]

st.text(palette)

cfg.configure_column(
    "c",
    cellEditor="agRichSelectCellEditor",
    valueFormatter=JsCode(
        """function(params) {
  if (Array.isArray(params.value)) {
    let stringjoin = "";
    params.value.forEach((el) => { if (el != null && el !== "") { stringjoin += ";" + el.name } });
    return stringjoin.slice(1);
  }
  return params.value.name;
}"""
    ),
    valueParser=JsCode(
        """function(params) { console.log(params);
const { newValue } = params.newValue;
if (newValue == null || newValue === "") {
  return null;
}
if (Array.isArray(newValue)) {
  let stringjoin = "";
  newValue.forEach((el) => { stringjoin += ";" + el.name });
  return stringjoin.slice(1);
}
return newValue;
}"""
    ),
    cellEditorParams={
        "values": palette,
        "multiSelect": "true",
        "suppressMultiSelectPillRenderer": "true",
        "formatValue": JsCode(
            """function(v) {
  if (v !== null || v !== "") {
    return `${v.name} (${v.code})`;
  }
} """
        ),
        "parseValue": JsCode(
            """function(v) {
  let stringjoin = "";
  console.log(v);
  forEach((el) => { stringjoin += ";" + el.name });
  return stringjoin.slice(1);
}"""
        ),
        "allowTyping": "true",
        "filterList": "true",
        "valueListMaxHeight": 220,
        "searchType": "matchAny",
    },
)

cfg.configure_grid_options(enableRangeSelection=True)

grid_opts = cfg.build()

grid_out = AgGrid(
    grid_df,
    gridOptions=grid_opts,
    enable_enterprise_modules=True,
    key="grid1",
    allow_unsafe_jscode=True,
)

What actually goes wrong

The editor receives objects while the cell stores and formats strings, and the formatter functions expect different shapes at different moments of the edit lifecycle. The in-editor preview tries to format a value that is not the expected object, which leads to undefined(undefined). In practice, this is a type consistency issue between what the grid stores, what the editor emits, and what the formatters assume.

The fix: keep types consistent end to end

The simplest way to stabilize the preview is to use strings as the editor values, keep the cell value as an array of those strings for multiSelect, and then format the display-only view by extracting the name part. With this, the dropdown shows name (code) while the cell renders Pink;Purple after commit, and the undefined(undefined) preview disappears.

import streamlit as st
import pandas as pd

from st_aggrid import AgGrid, GridOptionsBuilder, JsCode

grid_df = pd.DataFrame(
    "",
    index=range(5),
    columns=list("c"),
)

grid_df["c"] = [
    ["Pink (#FFC0CB)", "Purple (#A020F0)"],
    ["Purple (#A020F0)"],
    ["Blue (#0000FF)"],
    ["Green (#008000)"],
    ["Pink (#FFC0CB)"],
]

grid_df["c"] = grid_df["c"].astype("object")

cfg = GridOptionsBuilder.from_dataframe(grid_df)
cfg.configure_default_column(editable=True)

palette = [
    { "name": "Pink", "code": "#FFC0CB" },
    { "name": "Purple", "code": "#A020F0" },
    { "name": "Blue", "code": "#0000FF" },
    { "name": "Green", "code": "#008000" },
]

st.text(palette)

cfg.configure_column(
    "c",
    cellEditor="agRichSelectCellEditor",
    valueFormatter=JsCode(
        """function(params) {
  const { value } = params;
  if (Array.isArray(value)) {
    return value.map(item => item.toString().split("(")[0]).join(";");
  }
  return value;
}"""
    ),
    valueParser=JsCode(
        """function(params) { console.log(params);
  const { newValue } = params;
  if (newValue == null || newValue === "") {
    return null;
  }
  if (Array.isArray(newValue)) {
    return newValue;
  }
  return params.newValue.split(";");
}"""
    ),
    cellEditorParams={
        "values": list(map(lambda itm: itm["name"] + " (" + itm["code"] + ")", palette)),
        "multiSelect": "true",
        "allowTyping": "true",
        "filterList": "true",
        "valueListMaxHeight": 220,
        "searchType": "matchAny",
    },
)

cfg.configure_grid_options(enableRangeSelection=True)

grid_opts = cfg.build()

grid_out = AgGrid(
    grid_df,
    gridOptions=grid_opts,
    enable_enterprise_modules=True,
    key="grid1",
    allow_unsafe_jscode=True,
)

Why this matters

Multi-select editors are a frequent UX pattern in data tools, and Streamlit with Ag-Grid is no exception. Mixing dictionaries in the editor with serialized strings in the cell value leads to unpredictable previews and a brittle formatter chain. By aligning the value types flowing through the editor, parser, and formatter, you get stable previews during selection and predictable cell content after commit.

Conclusion

Keep your data model simple across agRichSelectCellEditor: let the editor offer strings such as name (code), keep the cell’s internal value as an array for multiSelect, and use valueFormatter to render a human-friendly Pink;Purple view. The example above demonstrates exactly that and removes the undefined(undefined) preview. As a housekeeping note, avoid redundant grid configuration calls to keep your setup tidy. With these small adjustments, the grid behaves consistently both while editing and after saving.