Text_input only getting set every other time?

Summary

I am having an issue where only every other text_input value gets set. Regardless whether I use tab or enter or return, the value only “takes” every other time.

Steps to reproduce

Code snippet:

def main():
    if 'topic' not in st.session_state:
        st.session_state.topic = "0"

    print(f"Before: Session State topic: {st.session_state.topic}")
    topic = st.text_input(label="Topic", value=st.session_state.topic)
    print(f"During: New Value: {topic}")
    st.session_state.topic = topic
    print(f"After: Session State topic: {st.session_state.topic}")

Run the above code. It displays a single text box, and initializes it to 0 on the first run. Enter a 1 and hit return. Select it again, and enter a 2. Select it again and enter a 3. Again with 4.

Expected behavior:

I would expect that the text box would always “take” the latest text entry, which would get saved into st.session_state.topic. I would expect the log to show:

Before: Session State topic: 0
During: New Value: 0
After: Session State topic: 0
Before: Session State topic: 0
During: New Value: 1
After: Session State topic: 1
Before: Session State topic: 1
During: New Value: 2
After: Session State topic: 2
Before: Session State topic: 2
During: New Value: 3
After: Session State topic: 3
Before: Session State topic: 3
During: New Value: 4
After: Session State topic: 4

Actual behavior:

Instead, every other entry is ignored. The log shows:

Before: Session State topic: 0
During: New Value: 0
After: Session State topic: 0
Before: Session State topic: 0
During: New Value: 1
After: Session State topic: 1
Before: Session State topic: 1
During: New Value: 1
After: Session State topic: 1
Before: Session State topic: 1
During: New Value: 3
After: Session State topic: 3
Before: Session State topic: 3
During: New Value: 3
After: Session State topic: 3

Debug info

  • Streamlit version: Streamlit, version 1.26.0
  • Python version: Python 3.8.16
  • Using Conda? No. PipEnv? No. PyEnv? Yes. Pex? No.
  • OS version: MacOS 13.5
  • Browser version: Safari Version 16.6 (18615.3.12.11.2)

Requirements file

streamlit~=1.25.0

Links

  • Link to your GitHub repo: Use above snippet instead; full app adds needless noise.
  • Link to your deployed app:

Additional information

If needed, add any other context about the problem here.
I see a very similar post from a year ago here, but it seems to have been closed without comment:

Hi @samkass, see if any one of the following approaches works for you:

Approach #1

import streamlit as st

def main():
    if 'topic' not in st.session_state:
        st.session_state.topic = "0"

    st.session_state.topic = st.text_input(label="Topic")

main()

OR

Approach #2

def callbk():
    st.session_state.topic = st.session_state.xyz

def main():
    if 'topic' not in st.session_state:
        st.session_state.topic = "0"

    st.session_state.topic = st.text_input(label="Topic", value=st.session_state.topic, key='xyz', on_change=callbk)

main()

Cheers

1 Like

Hi Shawn,
Thanks for the suggestion. The first one doesn’t really work for me, because I actually want to do something like:

def main():
    if 'topic' not in st.session_state:
        st.session_state.topic = f"{randint(0, 100)}"

    randomize = st.button("Randomize")
    if randomize:
        st.session_state.topic = f"{randint(0, 100)}"
    topic = st.text_input(label="Topic", value=st.session_state.topic)
    st.session_state.topic = topic

Where it initializes to a random value, and a button will re-randomize it, but a person can override the random value by typing a value explicitly.

The second approach you suggest does indeed work. However, I have about 8-10 fields I want to update, and having a separate callback for each would be a bit cumbersome. I’ll try creating a single callback which synchronizes the text fields with the session_state for all of them, and set them all to the same callback, and see how it goes.

Ideally, I would have loved to not use session_state at all, and just read/write directly to the text_input, but I can’t think of any way to satisfy the ability to explicitly initialize the value, allow a re-randomizer, and allow an explicitly typed value to override it to all work without session_state.

Hi @samkass, for the 2nd approach, you can probably use a common callback and pass the key via the argument parameter (of the widget) to the callback. That way, you can dynamically make the callback work for only an intended widget at any given point in time.

Cheers

Examining the 2nd approach, it occurred to me that if the callback could get the value from ‘xyz’, that perhaps I should just use “key” instead of “value”+assignment.

The below code seems to work, and uses that approach. (Let’s call this approach 3.) If there’s nothing idiomatically wrong with it, I would think this should be the normal pattern for a text_input which needs to assign to a session_state variable:

def main():
    if 'topic' not in st.session_state:
        st.session_state.topic = f"{randint(0, 100)}"

    randomize = st.button("Randomize")
    if randomize:
        st.session_state.topic = f"{randint(0, 100)}"
    st.text_input(label="Topic", key="topic") # instead of st.session_state.topic = topic, just assign it via "key"

I have incorporated “approach 3” into my larger application, and it seems to work well. It still seems like a bug that receiving the value then setting the session_state manually would lead to “skipped” values, as following the documentation would naturally lead to that pattern. I think the logic of how “value” affects the current text_input if the value is a session_state variable is currently technically undefined, and should probably be fixed to something where the original code works.

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.