streamlit-elements icon indicating copy to clipboard operation
streamlit-elements copied to clipboard

onChange event not working on Streamlit 1.34

Open dectoplate opened this issue 1 year ago • 21 comments

onChange event is not working after Streamlit 1.34 release.

dectoplate avatar May 21 '24 05:05 dectoplate

@okld is this repo still mantained?

we love your library and use it extensively throughout our projects Unfortunately this issue prevents us from upgrading Streamlit 😣

Thanks! 😀

vikvikvr avatar May 27 '24 07:05 vikvikvr

Bump

voidless-03 avatar Jun 16 '24 21:06 voidless-03

Bump x2 @okld it would be great if you could update it, Thank You.

moonbug-car avatar Jun 17 '24 15:06 moonbug-car

Bump @okld please!

KhabarovaNina avatar Jul 24 '24 16:07 KhabarovaNina

Hello!

This issue is related to the refactoring of streamlit in using custom components.

It is possible to restore the previous functionality by replacing in the file /streamlit_elements/core/callback.py: # from streamlit.components.v1 import components - OLD from streamlit.components.v1 import custom_component as components - NEW

However, I hope the streamlit developers will fix this issue in future releases.

KhabarovaNina avatar Jul 24 '24 17:07 KhabarovaNina

Thanks @KhabarovaNina a lot!

I made a small script to patch it automatically (needed for our Docker builds), hope it helps other people :)

import os
import streamlit_elements
import re

def patch_streamlit_elements():

    # issue: https://github.com/okld/streamlit-elements/issues/35
    relative_file_path = 'core/callback.py'
    library_root = list(streamlit_elements.__path__)[0]
    file_path = os.path.join(library_root, relative_file_path)

    # Read broken file
    with open(file_path, 'r') as file:
        lines = file.readlines()

    broken_import = 'from streamlit.components.v1 import components'
    fixed_import = 'from streamlit.components.v1 import custom_component as components\n'

    # Fix broken import line
    for index, line in enumerate(lines):

        if re.match(broken_import, line):
            print(f'Replaced broken import in {file_path}')
            lines[index] = fixed_import

    # Update broken file with fix
    with open(file_path, 'w') as file:
        file.writelines(lines)


if __name__ == "__main__":
    patch_streamlit_elements()

vikvikvr avatar Jul 24 '24 18:07 vikvikvr

vikvikvr

This is awesome, vikvikvr, thank you very much, the script still works great! 👍

Yeqishen avatar Sep 28 '24 07:09 Yeqishen

Hello everyone! Problem with onChange event is back in Streamlit 1.40.0 and fix import is not enough now :( If somebody fix it in new version - please, write solution here

KhabarovaNina avatar Nov 07 '24 23:11 KhabarovaNina

This code works in Streamlit 1.40.0.

def patch_modules_streamlit_elements(file: str, old_line: str, new_line: str):
    import streamlit_elements
    import os

    relative_file_path = "core/callback.py"
    library_root = list(streamlit_elements.__path__)[0]
    file_path = os.path.join(library_root, relative_file_path)

    with open(file_path, "r") as file:
        lines = file.readlines()

    is_changed = False
    for index, line in enumerate(lines):
        if old_line in line:
            print(f"Replacing line {index + 1} in {file_path}")
            lines[index] = line.replace(old_line, new_line)
            is_changed = True

    if is_changed:
        with open(file_path, "w") as file:
            file.writelines(lines)
        import importlib
        importlib.reload(streamlit_elements)

    return True

def patch_streamlit_elements():
    # fix 1.34.0
    patch_modules_streamlit_elements(
        "core/callback.py",
        "from streamlit.components.v1 import components",
        "from streamlit.components.v1 import custom_component as components\n",
    )


    #fix 1.40.0
    patch_modules_streamlit_elements(
        "core/callback.py",
        '        user_key = kwargs.get("user_key", None)\n',
        """
        try:
            user_key = None
            new_callback_data = kwargs[
                "ctx"
            ].session_state._state._new_session_state.get(
                "streamlit_elements.core.frame.elements_frame", None
            )
            if new_callback_data is not None:
                user_key = new_callback_data._key
        except:
            user_key = None
        """.rstrip()
        + "\n",
    )


if __name__ == "__main__":
    patch_streamlit_elements()

bonajoy avatar Nov 13 '24 02:11 bonajoy

@bonajoy Thank you so much!

KhabarovaNina avatar Nov 13 '24 09:11 KhabarovaNina

This code works in Streamlit 1.40.0.

def patch_modules_streamlit_elements(file: str, old_line: str, new_line: str):
    import streamlit_elements
    import os

    relative_file_path = "core/callback.py"
    library_root = list(streamlit_elements.__path__)[0]
    file_path = os.path.join(library_root, relative_file_path)

    with open(file_path, "r") as file:
        lines = file.readlines()

    is_changed = False
    for index, line in enumerate(lines):
        if old_line in line:
            print(f"Replacing line {index + 1} in {file_path}")
            lines[index] = line.replace(old_line, new_line)
            is_changed = True

    if is_changed:
        with open(file_path, "w") as file:
            file.writelines(lines)
        import importlib
        importlib.reload(streamlit_elements)

    return True

def patch_streamlit_elements():
    # fix 1.34.0
    patch_modules_streamlit_elements(
        "core/callback.py",
        "from streamlit.components.v1 import components",
        "from streamlit.components.v1 import custom_component as components\n",
    )


    #fix 1.40.0
    patch_modules_streamlit_elements(
        "core/callback.py",
        '        user_key = kwargs.get("user_key", None)\n',
        """
        try:
            user_key = None
            new_callback_data = kwargs[
                "ctx"
            ].session_state._state._new_session_state.get(
                "streamlit_elements.core.frame.elements_frame", None
            )
            if new_callback_data is not None:
                user_key = new_callback_data._key
        except:
            user_key = None
        """.rstrip()
        + "\n",
    )


if __name__ == "__main__":
    patch_streamlit_elements()

You sir are a LEGEND. Unfortunately this isnt working for me. Running python 3.11.0 and streamlit 1.40.0. This is what the callback file looks like for me and it isn't working.

image

TheUkrainian1991 avatar Nov 18 '24 17:11 TheUkrainian1991

@TheUkrainian1991 After the modification, you need to restart your streamlit application.

lkdd-ao avatar Dec 05 '24 01:12 lkdd-ao

@bonajoy Everything is normal, Thank you very much!

lkdd-ao avatar Dec 05 '24 01:12 lkdd-ao

@TheUkrainian1991 After the modification, you need to restart your streamlit application.

I have done. New terminal with new virtual environment same issue.

TheUkrainian1991 avatar Dec 05 '24 16:12 TheUkrainian1991

Anyone got it working for 1.44.0?

TheUkrainian1991 avatar Mar 26 '25 17:03 TheUkrainian1991

Anyone got a fix for 1.46.0?

TheUkrainian1991 avatar Jun 26 '25 14:06 TheUkrainian1991

This code works in Streamlit 1.40.0.

def patch_modules_streamlit_elements(file: str, old_line: str, new_line: str): import streamlit_elements import os

relative_file_path = "core/callback.py"
library_root = list(streamlit_elements.__path__)[0]
file_path = os.path.join(library_root, relative_file_path)

with open(file_path, "r") as file:
    lines = file.readlines()

is_changed = False
for index, line in enumerate(lines):
    if old_line in line:
        print(f"Replacing line {index + 1} in {file_path}")
        lines[index] = line.replace(old_line, new_line)
        is_changed = True

if is_changed:
    with open(file_path, "w") as file:
        file.writelines(lines)
    import importlib
    importlib.reload(streamlit_elements)

return True

def patch_streamlit_elements(): # fix 1.34.0 patch_modules_streamlit_elements( "core/callback.py", "from streamlit.components.v1 import components", "from streamlit.components.v1 import custom_component as components\n", )

#fix 1.40.0
patch_modules_streamlit_elements(
    "core/callback.py",
    '        user_key = kwargs.get("user_key", None)\n',
    """
    try:
        user_key = None
        new_callback_data = kwargs[
            "ctx"
        ].session_state._state._new_session_state.get(
            "streamlit_elements.core.frame.elements_frame", None
        )
        if new_callback_data is not None:
            user_key = new_callback_data._key
    except:
        user_key = None
    """.rstrip()
    + "\n",
)

if name == "main": patch_streamlit_elements()

You are our saviour sir, thank you sincerely! Tested on both 1.38.0 and 1.46.1 and this script is able to fix the issue for both of the versions! Thank you sincerely again

Ege-BULUT avatar Jul 09 '25 07:07 Ege-BULUT

Anyone got a fix for 1.46.0?

the above code is working fine (just tested with 1.46.1 and it seems working for me) Just dont forget to close your streamlit app beforehand and restart after the patch is complete

Ege-BULUT avatar Jul 09 '25 07:07 Ege-BULUT

what version of python is everyone using with a working fix?

TheUkrainian1991 avatar Aug 11 '25 17:08 TheUkrainian1991

Hello Everyone,

I followed the above

This code works in Streamlit 1.40.0.

def patch_modules_streamlit_elements(file: str, old_line: str, new_line: str): import streamlit_elements import os

relative_file_path = "core/callback.py"
library_root = list(streamlit_elements.__path__)[0]
file_path = os.path.join(library_root, relative_file_path)

with open(file_path, "r") as file:
    lines = file.readlines()

is_changed = False
for index, line in enumerate(lines):
    if old_line in line:
        print(f"Replacing line {index + 1} in {file_path}")
        lines[index] = line.replace(old_line, new_line)
        is_changed = True

if is_changed:
    with open(file_path, "w") as file:
        file.writelines(lines)
    import importlib
    importlib.reload(streamlit_elements)

return True

def patch_streamlit_elements(): # fix 1.34.0 patch_modules_streamlit_elements( "core/callback.py", "from streamlit.components.v1 import components", "from streamlit.components.v1 import custom_component as components\n", )

#fix 1.40.0
patch_modules_streamlit_elements(
    "core/callback.py",
    '        user_key = kwargs.get("user_key", None)\n',
    """
    try:
        user_key = None
        new_callback_data = kwargs[
            "ctx"
        ].session_state._state._new_session_state.get(
            "streamlit_elements.core.frame.elements_frame", None
        )
        if new_callback_data is not None:
            user_key = new_callback_data._key
    except:
        user_key = None
    """.rstrip()
    + "\n",
)

if name == "main": patch_streamlit_elements()

Hello Sir @bonajoy, @Ege-BULUT, The callback in the following example still doesn't work for me though I made the corresponding change in callback.py as suggested in the thread. I used Streamlit 1.46.1 (also tried 1.50.0) and python 3.13.3. Do you have a repo that's working so I can refer to? I would really appreciate any help and advice!

from streamlit_elements import elements, mui, sync
import streamlit as st

if "my_text" not in st.session_state:
    st.session_state.my_text = ""

with elements("sync_example"):
    # The onChange event triggers the sync() callback, which
    # stores the text field's value in st.session_state.my_text
    mui.TextField(
        label="Type something...",
        defaultValue=st.session_state.my_text,
        onChange=sync("my_text")
    )

    mui.Typography(f"Session State says: {st.session_state.my_text}")

calvinflorecruit avatar Oct 06 '25 04:10 calvinflorecruit

@calvinflorecruit @Ege-BULUT

Everyone!

Success for Streamlit version 1.50! Whoohoo!

I've been wracking my head against this for 3 days straight.

Finally found the fix:

https://github.com/streamlit/streamlit/pull/8633

The guy said this:

"raethlein commented on May 8, 2024 • Describe your changes In the past, some custom components have used a patch to register an on_change callback. Recently, we have done some refactoring that broke this workaround. This PR is a suggestion to extend our official API to make the patch redundant.

Note that we only want to pass the on_change_callback and not the args and kwargs. The register_widget function today uses args and kwargs as keywords to pass to the on_change callback. Besides the unfortunate naming - these are special keywords meant for functions themselves and not for pass-through arguments - we are thinking about deprecating them entirely, since you can wrap the callback easily to pass the arguments.

The way you can use it then looks like the following:

from functools import partial

import streamlit.components.v1 as components

_custom_component = components.declare_component("example", path=build_dir)

callback = partial(lambda x: f"On Change called with {x}", "some-value-for-x")

value = _custom_component("some-arg", other_arg="some-other-arg", on_change=callback)

"

So I worked with Claude Sonnet 4 and modified the streamlit-elements python files in the "core" folder.

Uploaded is what I have changed: callback.py, render.py, and frame.py

To use the "on_change".

And I have also uploaded an example Streamlit test page to show it's functionality "test_callback_functionality".

test_callback_functionality.py

callback.py frame.py render.py

oboefriend83 avatar Oct 21 '25 21:10 oboefriend83