question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

on_change not called for first session_state value

See original GitHub issue

Summary

Streamlit 0.84 introduces an on_change callback for some interactive widgets as part of the new Session State API. I’ve found that the callback is not invoked for the first value (i.e., the widget’s default or initial value), only for subsequent values.

Steps to reproduce

Code snippet:

import streamlit as st

def colour_changed():
    st.info(f"colour_changed: {st.session_state.colour}")

colour = st.selectbox("Colour", ["blue", "red", "yellow", "green"], key="colour", on_change=colour_changed)

st.info(f"selectbox returned: {colour}")
  1. When the report is first run, the selectbox returns blue as the selected value, but the colour_changed() callback is not called.
  2. When a different colour is selected, the selectbox returns the new colour and colour_changed() is called.

Expected behavior:

I believe the on_change callback should be called when a widget sets the initial/default value.

I’m finding that the lack of a callback for the initial value greatly complicates things because I can’t depend on the callback to do stuff for every value of an interactive widget. I have to write special cases for the initial state.

This is further complicated by what seems like a bug initializing default session_state values for widgets like selectbox (see #3598).

Actual behavior:

The on_change callback is not called for a widget’s initial/default value.

Is this a regression?

No. The Session State API is new in Streamlit 0.84.

Debug info

  • Streamlit version: Streamlit, version 0.84.2
  • Python version: Python 3.8.11
  • Using Conda? PipEnv? PyEnv? Pex? Just pip
  • OS version: Debian buster (in a python:3.8 container)
  • Browser version: Safari 14.1 on macOS Mojave 10.14.6

Additional information

None.


Community voting on feature requests enables the Streamlit team to understand which features are most important to our users.

If you’d like the Streamlit team to prioritize this feature request, please use the 👍 (thumbs up emoji) reaction in response to the initial post.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

4reactions
bulend-karadagcommented, Sep 8, 2022

I would like to reiterate the request of @kinghuang, I believe the on_change callback should be called when a widget sets the initial/default value. I have several selectbox chained dynamically. If there appear only one option in one of those selectboxes, the rest won’t work because I am not able to call on_change function. I mean that on_change callback is not called by the selectbox with one option.

2reactions
kinghuangcommented, Jul 28, 2021

Sure, I don’t have time to whip up a full example app right now, but I’ll try to describe the main bits.

I’m currently using Streamlit to build a series of review apps for our spaCy NLP pipelines. Using the new State API, I’ve been storing a database session object, a batch of rows for review, and a process object (basically a controller for what’s being reviewed). Central to the apps are custom Streamlit components for doing things like scoring the quality of the Doc objects or modifying labels in the entity spans in the Doc objects.

Here’s an abbreviated version of the function that is struggling with the on_change calls.

def process_and_batch_selector(process_title="Process", batch_title="Batch"):
    # Prepare data change handlers.
    def process_changed():
        st.session_state.batch_infos = st.session_state.process.batch_infos()

    def batch_changed():
        batch_info = st.session_state.batch_info
        if not batch_info:
            return

        # When a batch changes, commit any pending changes in the active session,
        # then create a new session for the batch.
        if session := st.session_state.session:
            session.commit()
            session.close()

        st.session_state.session = session = create_session()

        # Get the batch id from batch_info, and load a full object for the batch
        # into the session.
        process = st.session_state.process
        batch_id = batch_info[0]

        st.session_state.batch = session.query(process.batch_cls).filter(process.batch_cls.id == batch_id).first()

    # Initialize session state data.
    if not "session" in st.session_state:
        st.session_state.session = None
        st.session_state.batch = None

    # Select the review process.
    if process_title:
        st.subheader(process_title)
    process = st.selectbox("", PROCESSES, format_func=lambda p: p.label, key="process", on_change=process_changed)

    # Workaround for selectbox state and on_change limitations
    if not "batch_infos" in st.session_state:
        process_changed()

    if batch_title:
        st.subheader(batch_title)
    batch_info = st.selectbox("", st.session_state.batch_infos, format_func=lambda info: info[1], key="batch_info", on_change=batch_changed)

    # Workaround for selectbox state and on_change limitations
    if batch_info is not None and st.session_state.session is None:
        batch_changed()

When a batch_info is selected, I want to commit changes in the batch being edited (if any), then fetch the new batch of objects from the database for editing.

If you look for the # Workaround comments, you can see where the on_change functions are being explicitly invoked for both process and batch selection. Without them, the initial/default batch that is selected doesn’t load, for example, because the batch selectbox’s on_change function isn’t called.

If I didn’t set the key attribute on the batch selector on the batch selectbox and relied on its return value, I would get the value (including the initial/default value) on every run of the code. But, then, I’d have to implement my own change detection, probably by storing a value in session_state and then comparing it to the return value.

Read more comments on GitHub >

github_iconTop Results From Across the Web

onChange not getting called when @State var is modified
I'm working on a validation routine for a form, but when the validation results come in, the onChange is not being triggered.
Read more >
ASP.NET Core Blazor state management - Microsoft Learn
Learn how to persist user data (state) in Blazor apps.
Read more >
Javascript not calling for Onchange Event
Hi, Below is the code I have written to call Controller method from Javascript. But the issue I have identified is Onchange is...
Read more >
SCR19: Using an onchange event on a select element ... - W3C
Navigate to the trigger select element, navigate through the options but do not change the value. Check that the matching option values are...
Read more >
onChange(of:perform:) | Apple Developer Documentation
onChange is called on the main thread. Avoid performing long-running tasks on the main thread. If you need to perform a long-running task...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found