Component Error s.forEach is not a function

I have the error message below ,when I try to change the value of a cell of an Agrid table:
Component Error s.forEach is not a function

App running locally
Full text of the error message : Component Error s.forEach is not a function python
Streamlit and Python versions : Streamlit, version 1.38.0 , Python 3.12.4

my script below:

import streamlit as st
from streamlit.components.v1 import components, component_arrow
import pandas as pd
import re
from difflib import SequenceMatcher
from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode, ColumnsAutoSizeMode, JsCode
from io import BytesIO
from typing import List, Dict, Any
from functools import lru_cache

@lru_cache(maxsize=1000)
def normalize_text(text: str) -> str:
    return text.strip().lower() if pd.notna(text) else ""

@lru_cache(maxsize=1000)
def similarity(a: str, b: str) -> float:
    return SequenceMatcher(None, a, b).ratio()

def is_valid_email(email: str) -> bool:
    email_regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(email_regex, email))

def find_similar_contacts(df: pd.DataFrame, email_threshold: float = 0.8, global_threshold: float = 0.8) -> pd.DataFrame:
    required_columns = ['ContactID', 'Email', 'BU']
    if not all(col in df.columns for col in required_columns):
        raise ValueError(f"Le fichier doit contenir au moins les colonnes suivantes : {', '.join(required_columns)}")

    df['Email_norm'] = df['Email'].apply(normalize_text)
    df['Email valide'] = df['Email'].apply(is_valid_email)

    similar_data = []
    email_groups = {}
    group_counter = 0
    cross_bu_group_counter = 0
    cross_bu_groups = {}

    for i, row in df.iterrows():
        email_similar_contacts = set()
        unique_similar_emails = set()
        email_partial_similar_contacts = set()
        global_similar_contacts = set()
        global_partial_similar_contacts = set()
        bus_with_email = set()
        current_email_group = None
        current_cross_bu_group = None

        for j, other_row in df.iterrows():
            if i != j:
                email_sim = similarity(row['Email_norm'], other_row['Email_norm'])

                if email_sim >= email_threshold:
                    email_similar_contacts.add(other_row['ContactID'])
                    unique_similar_emails.add(other_row['Email'])
                    bus_with_email.add(other_row['BU'])
                    if email_sim < 1:
                        email_partial_similar_contacts.add(other_row['ContactID'])
                    
                    if other_row['Email'] in email_groups:
                        current_email_group = email_groups[other_row['Email']]
                    elif current_email_group is None:
                        current_email_group = group_counter
                        group_counter += 1

                    if email_sim == 1:
                        if other_row['Email'] in cross_bu_groups:
                            current_cross_bu_group = cross_bu_groups[other_row['Email']]
                        elif current_cross_bu_group is None:
                            current_cross_bu_group = cross_bu_group_counter
                            cross_bu_group_counter += 1

        unique_similar_emails.add(row['Email'])
        
        if current_email_group is None:
            current_email_group = group_counter
            group_counter += 1
        
        if current_cross_bu_group is not None:
            for email in unique_similar_emails:
                if similarity(email, row['Email']) == 1:
                    cross_bu_groups[email] = current_cross_bu_group
        
        for email in unique_similar_emails:
            email_groups[email] = current_email_group

        similar_data.append({
            'ContactID': row['ContactID'],
            'Email': row['Email'],
            'BU': row['BU'],
            'Nombre emails similaires': len(email_similar_contacts),
            'ContactID emails similaires': ", ".join(email_similar_contacts),
            'ContactID emails similaires partiel': ", ".join(email_partial_similar_contacts),
            'Email cross BU': ", ".join(bus_with_email),
            'Emails identique': bool(email_similar_contacts),
            'Emails partiellement similaires': bool(email_partial_similar_contacts),
            'Contact cross BU': len(bus_with_email) > 1,
            'Email valide': row['Email valide'],
            'Emails similaires uniques': ", ".join(unique_similar_emails),
            'Arbitrage contacts similaires': "",
            'Arbitrage contacts cross BU': "",
            'Groupe emails similaires': current_email_group,
            'Groupe cross BU': current_cross_bu_group
        })

    return pd.DataFrame(similar_data)

@st.cache_data
def to_excel(df: pd.DataFrame) -> bytes:
    output = BytesIO()
    with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
        df.to_excel(writer, index=False, sheet_name='Sheet1')
    return output.getvalue()

def create_grid(df: pd.DataFrame) -> Dict[str, Any]:
    gb = GridOptionsBuilder.from_dataframe(df)
    gb.configure_side_bar()
    gb.configure_default_column(groupable=True, filter=True, editable=False)
    return gb.build()

def download_button(df: pd.DataFrame, format: str, filename: str) -> None:
    if format == "CSV":
        data = df.to_csv(index=False)
        mime = 'text/csv'
    else:
        data = to_excel(df)
        mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    
    st.download_button(
        label=f"Télécharger la liste en {format}",
        data=data,
        file_name=filename,
        mime=mime,
    )

def function_test():
    st.markdown("<h1 style='text-align: center; color: #030F40;'>Référentiel  </h1>", unsafe_allow_html=True)
    st.markdown("####", unsafe_allow_html=True)
    uploaded_file = st.file_uploader("Téléchargez votre fichier Excel", type=["xlsx"])

    if uploaded_file is not None:
        try:
            df = pd.read_excel(uploaded_file)
            required_columns = ['ContactID', 'Email', 'BU']
            if not all(col in df.columns for col in required_columns):
                st.error(f"Le fichier doit contenir au moins les colonnes suivantes : {', '.join(required_columns)}")
                return

            perfect_duplicates = df[df.duplicated()]
            df = df.drop_duplicates()

            if not perfect_duplicates.empty:
                with st.expander("Doublons parfaits supprimés", expanded=True):
                    st.info("Les doublons parfaits suivants ont été supprimés automatiquement lors du chargement :")
                    st.dataframe(perfect_duplicates)

            results_df = find_similar_contacts(df)

            tabs = st.tabs(["Analyse des données chargées", "Arbitrage des contacts similaires", "Arbitrage des contacts cross BU"])

            with tabs[0]:
                st.info("Cette section représente les données chargées après analyse:")
                columns_to_display = ["ContactID", "Email", "BU", "Email valide", "Emails partiellement similaires", "Contact cross BU"]
                AgGrid(
                    results_df[columns_to_display],
                    gridOptions=create_grid(results_df[columns_to_display]),
                    enable_enterprise_modules=True,
                    update_mode=GridUpdateMode.VALUE_CHANGED,
                    fit_columns_on_grid_load=True,
                    theme="streamlit",
                    columns_auto_size_mode=ColumnsAutoSizeMode.FIT_ALL_COLUMNS_TO_VIEW,
                    editable=False
                )

                download_format = st.selectbox("Sélectionnez le format de téléchargement pour 'Analyse des données chargées'", ["CSV", "XLSX"], key="download_analyse")
                download_button(results_df, download_format, f'resultats_similarite.{download_format.lower()}')

            with tabs[1]:
                st.info("Cette section permet l'arbitrage des lignes de contacts similaires:")
                columns_similaires = [
                    "ContactID", "Email", "BU", "Emails partiellement similaires",
                    "Emails similaires uniques", "Arbitrage contacts similaires", "Groupe emails similaires"
                ]
                df_similaires = results_df[columns_similaires].copy()

                grid_options = create_grid(df_similaires)
                grid_options['columnDefs'] = [
                    {
                        'field': col,
                        'editable': col == "Arbitrage contacts similaires",
                        'cellEditor': 'agSelectCellEditor',
                        'cellEditorParams': {
                            'values': JsCode("params.data['Emails similaires uniques'].split(', ')")
                        } if col == "Arbitrage contacts similaires" else None
                    } for col in df_similaires.columns
                ]

                grid_response = AgGrid(
                    df_similaires,
                    gridOptions=grid_options,
                    enable_enterprise_modules=True,
                    update_mode=GridUpdateMode.VALUE_CHANGED,
                    fit_columns_on_grid_load=True,
                    theme="streamlit",
                    columns_auto_size_mode=ColumnsAutoSizeMode.FIT_ALL_COLUMNS_TO_VIEW,
                    allow_unsafe_jscode=True
                )

                download_format_similaires = st.selectbox("Sélectionnez le format de téléchargement pour 'Arbitrage des contacts similaires'", ["CSV", "XLSX"], key="download_similaires")
                download_button(df_similaires, download_format_similaires, f'arbitrage_contacts_similaires.{download_format_similaires.lower()}')
                
                if st.button("Sauvegarder les modifications (Contacts similaires)"):
                    updated_df = pd.DataFrame(grid_response['data'])
                    st.success("Les modifications ont été sauvegardées avec succès!")

            with tabs[2]:
                st.info("Cette section permet l'arbitrage des lignes de contacts cross BU:")
                columns_cross_bu = [
                    "ContactID", "Email", "BU", "Contact cross BU", "Email cross BU", "Arbitrage contacts cross BU", "Groupe cross BU"
                ]
                df_cross_bu = results_df[columns_cross_bu].copy()

                grid_options_cross_bu = create_grid(df_cross_bu)
                grid_options_cross_bu['columnDefs'] = [
                    {
                        'field': col,
                        'editable': col == "Arbitrage contacts cross BU",
                        'cellEditor': 'agSelectCellEditor',
                        'cellEditorParams': {
                            'values': JsCode("params.data['Email cross BU'].split(', ')")
                        } if col == "Arbitrage contacts cross BU" else None
                    } for col in df_cross_bu.columns
                ]

                try:
                    grid_response_cross_bu = AgGrid(
                        df_cross_bu,
                        gridOptions=grid_options_cross_bu,
                        enable_enterprise_modules=True,
                        update_mode=GridUpdateMode.VALUE_CHANGED,
                        fit_columns_on_grid_load=True,
                        theme="streamlit",
                        columns_auto_size_mode=ColumnsAutoSizeMode.FIT_ALL_COLUMNS_TO_VIEW,
                        allow_unsafe_jscode=True
                    )
                except Exception as e:
                    st.error(f"Une erreur s'est produite lors de l'affichage de la grille : {str(e)}")

                download_format_cross_bu = st.selectbox("Sélectionnez le format de téléchargement pour 'Arbitrage des contacts cross BU'", ["CSV", "XLSX"], key="download_cross_bu")
                download_button(df_cross_bu, download_format_cross_bu, f'arbitrage_contacts_cross_bu.{download_format_cross_bu.lower()}')
                
                if st.button("Sauvegarder les modifications (Contacts cross BU)"):
                    updated_df_cross_bu = pd.DataFrame(grid_response_cross_bu['data'])
                    st.success("Les modifications ont été sauvegardées avec succès!")

        except Exception as e:
            st.error(f"Une erreur s'est produite lors du traitement du fichier : {str(e)}")

if __name__ == "__main__":
    function_test()

Capture d’écran 2024-09-12 182411