Widget value snaps back every second time when read/write to file

Hello streamlit community!

I am having trouble writing an app which controls values which are written to yaml files (and read from the same files to initialise). I believe there should be a simple solution for this. Here is a minimal example:

test.yaml

var: 0.5

the naive approach

import streamlit as st
from yaml import load, dump, Loader, Dumper

with open('test.yaml', 'r') as f: 
    p = load(f, Loader = Loader)

for k, v in p.items(): 
    p[k] = st.slider(k, 0.0, 1.0, v, key = k)

with open('test.yaml', 'w') as f: 
    dump(p, f, Dumper = Dumper)

causes the widget to snap back to its former value every second time it is changed. I know this is expected behaviour due to the rerun upon widget change (although I still find it hard to properly wrap my head around this even after having used streamlit for a while). An almost perfect solution

import streamlit as st
from yaml import load, dump, Loader, Dumper

if not 'p' in st.session_state:
    with open('test.yaml', 'r') as f: 
        st.session_state['p'] = load(f, Loader = Loader)

p = {}
for k, v in st.session_state['p'].items(): 
    p[k] = st.slider(k, 0.0, 1.0, v, key = k)

with open('test.yaml', 'w') as f: 
    dump(p, f, Dumper = Dumper)

eliminates the snapping back and keeps the file and widget values in sync. The only problem with this solution is that my actual app has multiple pages and when I navigate to another page after changing the widget and come back the widget reloads with the initial value that is still stored in st.session_state['p'] which is not the value in the file at that point. I would need a way to pop p from st.session_state upon page reload or have some sort of โ€œpage session stateโ€ or keep the widget function caching over page changes for this solution to work.

I tried more complicated solutions without success by changing the order of execution of the statements with callback functions or calling st.rerun() under some conditions. It it becomes hard to follow through the execution of this simple code to find a solution that works.

I would be grateful for any help you can provide!

Hello :slight_smile:
I used to have the same problem in my app.
you can reset with a st.button()
if st.button(โ€˜r> esetโ€™)

st.session_state[โ€˜pโ€™]= False

if not โ€˜pโ€™ in st.session_state or st.session_state.get(โ€˜pโ€™):
with open(โ€˜test.yamlโ€™, โ€˜rโ€™) as f:
st.session_state[โ€˜pโ€™] = load(f, Loader = Loader)

I use streamlit option menu, that dosnโ€™t reload the page each time you switch the page. And could fix some problem !

Flo

Thank you for this very fast reply @Faltawer!

The reload button is certainly an acceptable option in many cases but I did not like that the user would have to remember to press the buttonโ€ฆ

The option menu component looks neat! I will consider using that.

1 Like

I found a solution here Mini-tutorial: Initializing widget values and getting them to stick without double presses - #2 by marduk
i.e. assign the widget value stored in session state to the widget initialiser value

import streamlit as st
from yaml import load, dump, Loader, Dumper

with open('test.yaml', 'r') as f: 
    p = load(f, Loader = Loader)

for k, v in p.items():
    if k in st.session_state: v = st.session_state[k]
    p[k] = st.slider(k, 0.0, 1.0, v, key = k)

with open('test.yaml', 'w') as f: 
    dump(p, f, Dumper = Dumper)

or to avoid unnecessary reads right after writes:

import streamlit as st
from yaml import load, dump, Loader, Dumper

if not 'p' in st.session_state:
    with open('test.yaml', 'r') as f: 
      st.session_state['p'] = load(f, Loader = Loader)

for k, v in st.session_state['p'].items():
    if k in st.session_state: v = st.session_state[k]
    st.session_state['p'][k] = st.slider(k, 0.0, 1.0, v, key = k)

with open('test.yaml', 'w') as f: 
    dump(st.session_state['p'], f, Dumper = Dumper)
1 Like

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