Text_input behavior for updating a session state value is not intuitive for my use-case

Summary

What I am trying to achieve: When the app loads, a default directory path will be stored in the session_state. A text_input box should display the current directory when the app loads. This should be editable (like when value is used instead of placeholder). The user should be able to edit the directory path and upon hitting enter, the current directory will be stored and used based on the user input.

Steps to reproduce

I am having issues getting the exact behavior I am looking for. I can either get it so that I use a value but the session state variable used to store the current directory needs to be updated twice sometimes (issue 1), I just use a placeholder which works but the text is not editable (issue 2), or I use a key and value, which gives me the warning (issue 3). I know the warning only loads once when the app is launched, but it is not something I want users to see every time they launch the app.

Issue 1

The directory entered needs to be entered twice in order for session_state.current_directory to be updated. Similar post (Streamlit: Form Button has to be clicked twice).

Code snippet:

import streamlit as st


state = st.session_state

def change_directory():
    if state.current_directory != state.updated_directory:
        state.current_directory = state.updated_directory

if "current_directory" not in state:
    state.current_directory = "D:/default_directory"
if "updated_directory" not in state:
    state.updated_directory = ""

state.current_directory = st.text_input("Enter a directory (issue 1)", value=state.current_directory)
st.write(state.current_directory)

# state.updated_directory = st.text_input("Enter a directory (issue 1)", value=state.current_directory)
# change_directory()
# st.write(state.current_directory)

Here are the two ways I attempted. Comment out and uncomment the relevant lines of code to see the other way I tried. In both cases, this will work the first time a new directory is entered. Upon hitting enter, the value updates. However, every time after, you must enter a directory twice in order for the value to be updated. Oddly, when you have to enter it twice, the first attempt that does not work is disregarded. I do not understand how exactly Streamlit executes the code so it is very weird that for every pair of values entered, the first one does nothing, and the second one correctly updates the value.

Issue 2

Using placeholder will show the value of the directory, but is not editable. This is an issue because the directory paths could be very long and if for instance, a user has different folders they want to switch between where the paths are something like “D:/base directory/subdirectory/by month/january” , “D:/base directory/subdirectory/by month/february”, etc… having to type the full directory path is not ideal. I want the path to be editable, so they can just change the month name.

Code snippet:

import streamlit as st


state = st.session_state

def change_directory():
    if state.current_directory != state.updated_directory:
        state.current_directory = state.updated_directory

if "current_directory" not in state:
    state.current_directory = "D:/default_directory"
if "updated_directory" not in state:
    state.updated_directory = ""

st.text_input("Enter a directory (issue 2)", key="updated_directory", placeholder=state.current_directory, on_change=change_directory)
st.write(state.current_directory)

This works for only needing to enter the new directory once, but the placeholder value is greyed out and not editable.

Issue 3

Using a key and value gets closer to the desired behavior. This will solve both issues 1 and 2. However, it is not perfect. I know Streamlit does not suggest using a key and value, since a warning will show once the app is loaded. This is not ideal since the user might think there is a bug in the app. This also doesn’t quite solve the issues I’m having because the initial directory shown will be blank. After a user enters a new directory once, the text will show and will be editable. But when the app is launched, the default directory will not show. Using a placeholder can fix that, but the default directory is not editable and only after it is changed once it will be, which is not ideal for the user.

Code snippet:

import streamlit as st


state = st.session_state

def change_directory():
    if state.current_directory != state.updated_directory:
        state.current_directory = state.updated_directory

if "current_directory" not in state:
    state.current_directory = "D:/default_directory"
if "updated_directory" not in state:
    state.updated_directory = ""

st.text_input("Enter a directory (issue 3)", key="updated_directory", placeholder=state.current_directory, value=state.current_directory, on_change=change_directory)
st.write(state.current_directory)

Expected behavior:

I would like a user to be able to launch the app with no warning, have a text_input with a default directory displayed that is editable, and each time they edit/change the directory, the current directory updates correctly.

Actual behavior:

See above.

Debug info

  • Streamlit version: 1.19.0
  • Python version: 3.9.12
  • Using venv
  • OS version: Microsoft Windows 11 Home 10.0.22621 Build 22621
  • Browser version: Chrome Version 110.0.5481.178 (Official Build) (64-bit)

Requirements file

streamlit
omegaconf
Pillow

1 Like

I have been following your posts in the other topic and was about to reply. I had a similar question a couple months ago and I believe what you want to achieve is the following:

import streamlit as st

state = st.session_state
if 'directory' not in state:
    state._directory = "D:/default_directory"

def change_directory():
    state._directory = state.directory

st.json(state)  # just for debugging, see what is happening here

st.text_input("Enter a directory", value=state._directory, key="directory", on_change=change_directory)
st.write(state.directory)

st.json(state)  # just for debugging, see what is happening here too

The key (no pun intended) is to initialize the widget with a value that is NOT the widgets key from session_state.
Credits go to @asehmi who came up with this when I had this question: Make a widget remember its value after it is hidden and shown again in later script runs

EDIT:
Actually the code above is only needed when you plan on hiding a widget and want it to remember its value (cf. my question). For your use-case, the following is sufficient:

import streamlit as st

state = st.session_state
DEFAULT_DIR = "D:/default_directory"

st.json(state)  # just for debugging, see what is happening here

st.text_input("Enter a directory", value=DEFAULT_DIR, key="directory")
st.write(state.directory)

st.json(state)  # just for debugging, see what is happening here too
1 Like

Re: Issue 1
When you update some widget parameter with its output, that can lead to those “double submit” scenarios. Here’s one such case I explained in another post:

I believe @Wally’s answer above covers your use case, so I’ll stop there. :slight_smile:

Wow…that is super simple. Thank you! I think I missed the definition of value in the docs where it literally says it’s the value when it first renders. Thank you again!

I very much appreciate the help. Your tutorial is very helpful. Thank you!!

1 Like

No worries. It becomes tricky though, if you want to temporarily hide widgets and have them re-appearing while remembering their last value (happens commonly when building a multi-page app with widgets scattered among different pages/apps). In this case, you have to use dummy/shadow keys within your session_state that get set by a callback function, as I showed in the first code snippet. Thus the widget will “remember” its value (it’s more like: it will be created again with its default value set to its last known value).

1 Like

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