Forms weird state behaviour

I’m working on an app intended to be used to update a json file. I’ve read a json file initialized each json field as a state and create widgets with the same key and after submitting with the forms I would use the state to create a new json file with the newer field values.

The structure is similar to this example

import streamlit as st 

keys = ["a", "b", "c", "d", "e"]

# We know that:
# a -> 0 
# b -> 1 
# c -> 2
# d -> 3 
# e -> 4
for idx, key in enumerate(keys):
    if key not in st.session_state:
        st.session_state[key] = idx
        
 
st.write("# Some Header")

with st.form("Form 1"):
    with st.expander("First Form"):
        st.slider("a", 0, 10, step=1, key= "a")
        st.slider("b", 0, 10, step=1, key='b')
    st.form_submit_button("Submit Here I")
    
with st.form("Form 2"):
    with st.expander("Second Form"):
        st.slider("c", -1, 10, step=1, key= "c")
        st.slider("d", -1, 10, step=1, key='d')
    st.form_submit_button("Submit Here II")
    
with st.expander("Other Thin outside form"):
    st.slider("e", 0, 10, step=1, key= "e")
    st.button("Do something")
    
st.write([f"{key}: {st.session_state[key]}" for key in sorted(st.session_state.keys())])

Everything was okay until I’ve noticed that if I submit one of the forms without opening all the expanders the value that appears in the sliders would be the min_value instead of the state value.

Any help would be very appreciated :grin:

Can someone please help?

Hi @Eduardo_Pacheco!

So, what’s happening here is that by assigning a key to each slider of the same letter as the keys you already stored in the state. You have literally linked those values to whatever the slider value is.

This means that the session state will always reflect the value that the slider is at the time that the user selects the form_submit_button. I think, if I understand your use case, you want the user to be able to choose to change the value, or not (ie. some values might change but some may stay the same).

I think the easiest way to do this is by adding the parameter value to your sliders and assigning that value to be the initialized value for each key your are tracking. Check out the code snippet where I do that here:

import streamlit as st

keys = ["a", "b", "c", "d", "e"]

# We know that:
# a -> 0
# b -> 1
# c -> 2
# d -> 3
# e -> 4
for idx, key in enumerate(keys):
    if key not in st.session_state:
        st.session_state[key] = idx

st.write("# Some Header")

with st.form("Form 1"):
    with st.expander("First Form"):
        st.slider("a", 0, 10, step=1, value=st.session_state.a, key= "a")
        st.slider("b", 0, 10, step=1, value=st.session_state.b, key='b')
    st.form_submit_button("Submit Here I")

with st.form("Form 2"):
    with st.expander("Second Form"):
        st.slider("c", -1, 10, step=1, value=st.session_state.c, key= "c")
        st.slider("d", -1, 10, step=1, value=st.session_state.d,key='d')
    st.form_submit_button("Submit Here II")

with st.expander("Other Thin outside form"):
    st.slider("e", 0, 10, step=1,value=st.session_state.e, key= "e")
    st.button("Do something")

st.write([f"{key}: {st.session_state[key]}" for key in sorted(st.session_state.keys())])

Now, when the slider renders in your browser, it will have the default value you assigned in the state, and if the user doesn’t update that value it will not change to the min_value for the slider.

Happy Streamlit-ing!
Marisa

1 Like

Hi @Marisa_Smith

Ohh, that was an easy fix. I thought that by setting the key as the state the widget value would be linked to the state value, not the other way around. Thanks for the help!! :smiley:

2 Likes