The following widgets are part of an image gallery. Each displayed image has a name (e.g. “imageA”) and can be tagged (e.g. “tag1”). The user should be able to filter by name and/or tags. So I used two multiselect widgets. The widget’s options should only show the names/tags that are possible with the current filter selection. The name filter selections should be linked with a logical OR. The selection of the tag filter should be linked with all selected names with a logical AND.
So this is an example of the logical combination: (imageA OR imageB) AND tag1
The problem is: Whenever I select both a name and a tag, the name filter selection is reset.
Steps to reproduce
Code snippet:
import streamlit as st
images=[{"name": "imageA", "tags": ["tag1", "tag2"]},
{"name": "imageB", "tags": ["tag1"]},
{"name": "imageC", "tags": []}]
if "name_filter_options" not in st.session_state:
st.session_state["name_filter_options"] = ["imageA", "imageB", "imageC"]
if "tag_filter_options" not in st.session_state:
st.session_state["tag_filter_options"] = ["tag1", "tag2"]
if "name_filter" not in st.session_state:
st.session_state["name_filter"] = []
if "tag_filter" not in st.session_state:
st.session_state["tag_filter"] = []
if not st.session_state.tag_filter:
st.session_state.name_filter_options = ["imageA", "imageB", "imageC"]
else:
for image in images:
if not set(image["tags"]).intersection(set(st.session_state.tag_filter)):
st.session_state.name_filter_options.remove(image["name"])
st.multiselect(label="Filter by names", key="name_filter", options=st.session_state.name_filter_options)
st.multiselect(label="Filter by tags", key="tag_filter", options=st.session_state.tag_filter_options)
st.write("Name filter: " + str(st.session_state.name_filter))
st.write("Tag filter: " + str(st.session_state.tag_filter))
Steps to reproduce:
Select “imageA” in first multiselect widget
Select “tag1” in second multiselect widget
Expected behavior:
The first multiselect widget should still have the selected option “imageA” and the second “tag1”.
Similarly, the output should be:
Name filter: [‘imageA’]
Tag filter: [‘tag1’]
Actual behavior:
The selection of the first multiselect widget is reset, but the options are updated.
The output is:
Name filter:
Tag filter: [‘tag1’]
Debug info
Streamlit version: 1.19.0
Python version: 3.8.10
Using Conda? PipEnv? PyEnv? Pex?
OS version:
Browser version:
Requirements file
Using Conda? PipEnv? PyEnv? Pex? Share the contents of your requirements file here.
Not sure what a requirements file is? Check out this doc and add a requirements file to your app.
Links
Link to your GitHub repo:
Link to your deployed app:
Additional information
If needed, add any other context about the problem here.
import streamlit as st
images=[{"name": "imageA", "tags": ["tag1", "tag2"]},
{"name": "imageB", "tags": ["tag1"]},
{"name": "imageC", "tags": []}]
if "name_filter_options" not in st.session_state:
st.session_state["name_filter_options"] = ["imageA", "imageB", "imageC"]
if "tag_filter_options" not in st.session_state:
st.session_state["tag_filter_options"] = ["tag1", "tag2"]
name = st.multiselect(label="Filter by names", key = "name", options=st.session_state.name_filter_options)
tag = st.multiselect(label="Filter by tags", key = "tag", options=st.session_state.tag_filter_options)
if not st.session_state.tag:
st.session_state.name_filter_options = ["imageA", "imageB", "imageC"]
else:
for image in images:
if not set(image["tags"]).intersection(set(st.session_state.tag)):
st.session_state.name_filter_options.remove(image["name"])
@mliu Thank you for your quick response.
Unfortunately the name filter options are not updated after setting a tag. So after entering e.g. “tag1” in the tag filter, the name filter should only suggest images with this tag (i.e. “imageA” and “imageB” in the case of “tag1”).
i think that is another issue where “imageC” does not have anything to remove from
for image in images:
if not set(image["tags"]).intersection(set(st.session_state.tag)):
st.session_state.name_filter_options.remove(image["name"])
you can add a condition like
else:
for image in images:
if len(image) == 0:
continue
if not set(image["tags"]).intersection(set(st.session_state.tag)):
print(st.session_state.name_filter_options)
print(image["name"])
st.session_state.name_filter_options.remove(image["name"])
Unfortunately that doesn’t solve it either.
The problem also occurs when I change the widget’s options by simply overriding the st.session_state.name_filter_options instead of removing items from it.
It’s the same in this code from Shawn_Pereira: Update options of multiselect widget
If a function is selected and then a function is added, the previous selection is reset even if the function still exists as option.
Could it be that whenever the options were changed from the outside, it creates a whole new widget, which of course resets the widget’s selection? If so, is there a way to keep the selection?
Changing any of the parameters used to create a widget will indeed create a new widget. Additionally, the key for a widget is removed from session state when it is destroyed. Hence, changing the options of the multiselect widget causes the state of the widget to be destroyed and reinitialized. You will need to copy the selection information in some way to circumvent this.
Here is a toy example:
import streamlit as st
all_options = ['A','B','C','D','E','F','G']
if '_selected' not in st.session_state:
st.session_state._possible = all_options
st.session_state._selected = []
for option in all_options:
st.session_state[option] = True
st.session_state.selected = st.session_state._selected
def update(key):
if st.session_state[key] == True and key not in st.session_state._possible:
st.session_state._possible.append(key)
st.session_state._possible.sort()
elif key in st.session_state._possible:
st.session_state._possible.remove(key)
if key in st.session_state._selected:
st.session_state._selected.remove(key)
for option in all_options:
st.checkbox(option, key=option, on_change=update, args=[option])
def key_protect():
st.session_state._selected = st.session_state.selected
st.multiselect('Choose',st.session_state._possible, key='selected', on_change=key_protect)
The point to focus on is copying the widget state to the _selected key any time it is changed and then overwriting the widget state from that copied value at the beginning of the script.
# Get widget state from copied value
st.session_state.selected = st.session_state._selected
# Callback to copy widget state whenever it is changed
def key_protect():
st.session_state._selected = st.session_state.selected
I have extra lines in there to make sure a selection is removed if it is no longer a valid option, but hopefully you can extract the necessary details for your case.
I hope this is mention in document somewhere, i did not found it so far; i was keep trying to set key session now got to know it is getting destroy on re run, hence was not getting my excepted output. Thank you.
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.