Text input/button/check box clear session state

For my app, I want the text input to have default value based on a df value reading from the database. The user has right to put whatever they want into the text_input. However, when the user click the button, it will save the new user input to the dataframe and trigger the whole process to rerun so we will reread default_value from database, and set it as a default value to the text input.

Notice the default_value is changing since we are updating the database.

I checked the on_click method but it seems only setting the text_input value to something fixed or cannot get my database change because I do the db change after have the button clicked.

default_value = get_a_df_from_db().iloc[0][0]
re = st.text_input('reuu', key='teuust', value=default_value)

rr = st.button('test',  key='vv')
if rr:
  store re to database

Please suggest how should I do under this circumstance.

Thanks.

Here is an illustration:

import streamlit as st
import pandas as pd

#########################
# A fake data source;
# Pretend this isn't here
#########################
if 'my_db' not in st.session_state:
    st.session_state.my_db = pd.DataFrame({'A':['a','b'],'B':['c','d']})
#########################


# Read/Write Commands for your data source
def get_from_db():
    '''A function that queries your database
    '''
    return st.session_state.my_db

def write_to_db(row, column, value):
    '''A function that writes to your database
    '''
    st.session_state.my_db.loc[row,column] = value


# Update script to run when user commits a change
def commit_change(row, column, value):
    '''A function to update your database and refresh your data from source
    '''
    write_to_db(row, column, value)
    st.session_state.df = get_from_db()


# Use session state to only read from source on first load
if 'df' not in st.session_state:
    st.session_state.df = get_from_db()


st.subheader('Current Database')
st.write(st.session_state.df)

column = st.selectbox('Column',st.session_state.df.columns)
row = st.selectbox('Row',st.session_state.df.index)

current_value = st.session_state.df.loc[row,column]
st.session_state.default = current_value

new = st.text_input(f'Cell {column}{row}',value=st.session_state.default)

st.button('Commit', on_click=commit_change, args=[row,column,new])

FC870AF9-7B44-47BE-A491-DAEAD2290A58

Hi @mathcatsand ,

Thanks so much for your reply. Under your example, my question would be like:

new = st.text_input(f'Cell {column}{row}', value='please put input inside')

So I always want the text input to display “please put input inside” after the user click commit and the page refreshes. The user can type in whatever they want afterwards, but I just want to have that default value always being dispalyed as something unchanged. And after hitting the commit button, the df will be refreshed as what you have in your current example.

The workflow should be like:

  1. text input display ‘please put input inside’
  2. user type something for that
  3. user click commit, df gets refreshed, the text input gets back to display ‘please put input inside’
  4. iterative we go back to step 2

In the current code, after hitting the commit button, the text input keep having the last input value instead of my default setting.

Then instead of using a default value, you use a key with the widget and overwrite the stored value. You can use the placeholder keyword argument if you want “instruction text” written within the empty widget.

import streamlit as st
import pandas as pd

#########################
# A fake data source;
# Pretend this isn't here
#########################
if 'my_db' not in st.session_state:
    st.session_state.my_db = pd.DataFrame({'A':['a','b'],'B':['c','d']})
#########################


# Read/Write Commands for your data source
def get_from_db():
    '''A function that queries your database
    '''
    return st.session_state.my_db

def write_to_db(row, column, value):
    '''A function that writes to your database
    '''
    st.session_state.my_db.loc[row,column] = value


# Update script to run when user commits a change
def commit_change(row, column, value):
    '''A function to update your database and refresh your data from source
    '''
    write_to_db(row, column, value)
    st.session_state.df = get_from_db()
    st.session_state.input = ''


# Use session state to only read from source on first load
if 'df' not in st.session_state:
    st.session_state.df = get_from_db()


st.subheader('Current Database')
st.write(st.session_state.df)

column = st.selectbox('Column',st.session_state.df.columns)
row = st.selectbox('Row',st.session_state.df.index)

current_value = st.session_state.df.loc[row,column]

new = st.text_input(f'Enter New Cell Value for {column}{row}',key='input', placeholder='Enter value here')

st.button('Commit', on_click=commit_change, args=[row,column,new])

66DA6820-1A43-4581-9CB0-A8CA35D00986

Hi @mathcatsand ,

Thanks so much for your solution! I’m still confused about one part. I am trying to understand those strange behaviour when session state gets update for buttons/text inputs.

Workflow:

  1. User type something in ‘Enter New Cell Value’ and hit commit (as for my understanding, since the session state variable is changed, so the program does a rerun, and hence the ‘should stay the same as default text input’ get the new default_value (since commit changes db) cell value.
  2. User type something in ‘Should stay the same as default’ and click the button test. However under this situation, whatever user just typed in ‘Should stay the same’ is still there instead of the default value. Why is this? Shouldn’t it be the same as since the the user types in something/clicks the button, so session state variable changed, so the program does a rerun, hence it displayes the default value?

The other code stays the same except for this:

if 'my_db' not in st.session_state:
    st.session_state.my_db = pd.DataFrame({'A':['a','b'],'B':['c','d']})

def get_from_db():
    return st.session_state.my_db
def write_to_db(value):
    st.session_state.my_db.insert(0, value, [value, value])
def commit_change(value):
    write_to_db(value)
    st.session_state.df = get_from_db()
if 'df' not in st.session_state:
    st.session_state.df = get_from_db()

st.subheader('Current Database')
st.write(st.session_state.df)

rr = st.button('test',  key='vv')
default_value = st.session_state.my_db.iloc[0][0]
for i in range(1):
    test1 = st.text_input('Should stay the same as default value after clicking button test', key='test1' + str(i), value=default_value)

new = st.text_input(f'Enter New Cell Value',key='input', placeholder='Enter value here')
m = st.button('Commit', on_click=commit_change, args=[new])

I don’t understand why you removed the functionality from commit_change. It’s not doing anything anymore.

Are you wanting to send changes to your database or no?

Appologize I accidentally deleted that line of code. Already re-edited my post above. Sorry for the impact!

You need to use a key for the widget and not the value keyword parameter. If there is no change to the construction of the widget it will retain any edits a user has given it.

By using a key in session state and manually assigning a value to that key, you can control the state of the widget. The default value only impacts the creation of the widget; if there is no change to the creation of the widget, then any reruns of the pages will have the widget remembering what the user last entered. It doesn’t matter how many times you query a new “default value.” If you keep pulling the same default value, then Streamlit doesn’t know that it’s not the same widget.

If you want to “refresh” a widget you either have to manually assign its state through its key or destroy it (and create a new one).

You can destroy a widget by using a different key or changing any of its defining parameters. Otherwise, you can destroy it by not rendering it on the page (and re-rendering it on a later rerun).

I’m having trouble understanding. Please can you rephrase what your remaining question is?

If you want to clear the widget when you enter a new value to commit, you need to put the line back into the commit function that assigns an empty string to that widget.

If I change the construction of one button of the page, will the whole page’s items all being refreshed?
(i.e. I change only one widget, the whole page knows I have some change, hence the whole pages widgets get a refresh?)

So in workflow step 1: since the commit button’s on_click method captures a new change (changing any of its defining parameters), so streamlit recognizes it as a new widget change, so the whole pages’ buttons/text inputs get refreshed, hence the value=default_value worked

In workflow step2: although the default_value itself changed, it is not treated as a change for value=default_value (this is not changing defining parameters), so not treated as a widget change, so nothing changed on page.

Am I understanding this correctly?

Thanks.

If you change the construction of a single widget, only that widget will be reconstructed from scratch. All other widgets that haven’t had their parameters changed will keep their memory of what the user had last entered.

The arguments for the on_click and on_change are as little different. Widgets won’t be reconstructed from different values passed to args. The values within args are things that are expected to change.

In workflow 1, the test input widget is constructed with a different default value, so that widget in and of itself is reconstructed from scratch because it has a new build parameter. The buttons and other input saw no change in construction and were not affected. If anything had been changed in the second input, it would continue to remember that value.

In workflow 2, the default value did not change so the test input widget retains its memory (i.e. its state) and is not reconstructed. What you see in the widget is the user input and not the “default value” if the user has entered anything into it since construction. Furthermore, the text input for committing changes has no change to its build parameters and is not reconstructed; it remembers whatever the user last had there, too.

3 Likes

You perfectly solved my question and I finally understand everything!!! Thanks so much for your help!!!

2 Likes

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