Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bad behavior with set_query_params #6907

Closed
3 of 5 tasks
noamgat opened this issue Jun 27, 2023 · 5 comments
Closed
3 of 5 tasks

Bad behavior with set_query_params #6907

noamgat opened this issue Jun 27, 2023 · 5 comments
Labels
feature:query-params status:needs-triage Has not been triaged by the Streamlit team type:bug Something isn't working

Comments

@noamgat
Copy link

noamgat commented Jun 27, 2023

Checklist

  • I have searched the existing issues for similar issues.
  • I added a very descriptive title to this issue.
  • I have provided sufficient information below to help reproduce this issue.

Summary

st.experimental_set_query_params does not always write its value to the browser.
In the following example, the write will fail exactly every other time - the new selection will be discarded every 2nd selection.

Reproducible Code Example

Open in Streamlit Cloud

from typing import List, Any

import streamlit as st


def selectbox_with_query_storage(label: str, options: List[Any], query_param_name: str, **kwargs):
    default_index = 0
    current_query_params = st.experimental_get_query_params()
    current_value_params = current_query_params.get(query_param_name, [])
    current_value = None
    if len(current_value_params) == 1:
        current_value_str = current_value_params[0]
        current_value = type(options[0])(current_value_str)  # Convert to type based on first option.
        try:
            default_index = options.index(current_value)
        except ValueError:
            pass
    value = st.selectbox(label, options, index=default_index, **kwargs)
    if value != current_value:
        current_query_params[query_param_name] = value
        st.experimental_set_query_params(**current_query_params)
    return value


val = selectbox_with_query_storage("Remember me", ["a", "b", "c", "d", "e"], query_param_name="choice")
st.write(val)

Steps To Reproduce

  1. Select a value in the dropdown. The value and url will be updated.
  2. Select a value in the dropdown. The value and url will NOT be updated.
  3. Select a value in the dropdown. The value and url will be updated.
  4. Select a value in the dropdown. The value and url will NOT be updated.

Expected Behavior

The value is always updated

Current Behavior

The value is not updated every other time

Is this a regression?

  • Yes, this used to work in a previous version.

Debug info

  • Streamlit version: Latest
  • Python version: 3.11
  • Operating System: Windows 11
  • Browser: Chrome
  • Virtual environment: Conda

Additional Information

No response

Are you willing to submit a PR?

  • Yes, I am willing to submit a PR!
    (Did not mark because I don't know how to solve the issue...)
@noamgat noamgat added status:needs-triage Has not been triaged by the Streamlit team type:bug Something isn't working labels Jun 27, 2023
@noamgat
Copy link
Author

noamgat commented Jun 28, 2023

Deployed reproducing code here:

https://noamgat-streamlit-example-streamlit-app-x1gje6.streamlit.app/

@noamgat
Copy link
Author

noamgat commented Jul 2, 2023

I was able to workaround the bug, writing the solution here in case anyone finds it in the future.
The problem was, that if the index=___ parameter is passed to selectbox() , it is part of the ID. So if the index changes throughout the lifecycle of the app, the widget becomes a different one and the state breaks. The solution was to inject the default value not through the index=___ parameter, but through the session state. Fixed code below:

def selectbox_with_query_storage(label: str, options: List[Any], query_param_name: str, **kwargs):
    key = kwargs.pop("key", f"selectbox_with_query_storage_{query_param_name}")
    has_value_from_previous_renders = key in st.session_state

    current_query_params = st.experimental_get_query_params()
    current_value_params = current_query_params.get(query_param_name, [])
    query_value = None
    if len(current_value_params) == 1:
        query_value_str = current_value_params[0]
        query_value = type(options[0])(query_value_str)  # Deduce type from first option and convert.
    # There are two ways to set a preselected value in a selectbox - using the session state and the index=___ param.
    # The index=___ method causes problems if the index changes during the run, so we opt for the session state.
    # https://discuss.streamlit.io/t/set-selectbox-value/18376/4
    if query_value and not has_value_from_previous_renders:
        st.session_state[key] = query_value
    value = st.selectbox(label, options, key=key, **kwargs)
    if value != query_value:
        current_query_params[query_param_name] = value
        st.experimental_set_query_params(**current_query_params)
    return value

@noamgat noamgat closed this as completed Jul 2, 2023
@Asaurus1
Copy link
Contributor

Asaurus1 commented Jul 7, 2023

@noamgat I am not able to reproduce the bug on https://issues.streamlit.app/?issue=gh-6907&choice=c in a Chromium browser exactly as you've described it. However I do remember encountering a similar issue while developing https://github.com/Asaurus1/streamlit-qs/. My understanding there was that the problem stems from the order in which values get saved to the URL vs session state when you force a page reload.

The way I got around this in streamlit-qs was to only call experiemental_set_query_params in the on_change callback function of the selectbox. This forces the URL to update "in sync" with session_state and avoids the issue. Streamlit-qs does some magic on the backend to basically inject this experimental_set_query_params call ahead of any custom on_change function you specified, so I think it works pretty transparently.

@noamgat
Copy link
Author

noamgat commented Jul 7, 2023 via email

@hoggatt
Copy link

hoggatt commented Sep 18, 2023

I had to use a combination of both solutions for it to work for me. I had to use both the session_state and on_change for it to work correctly

def update_query_params():
    print("updating to",st.session_state.itrselect)
    st.experimental_set_query_params(itr=st.session_state.itrselect)
    
item = st.selectbox(
    "Select an item:",
    items,
    on_change=update_query_params,
    index=itr_index,
    key="itrselect",
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature:query-params status:needs-triage Has not been triaged by the Streamlit team type:bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants