Sharable Streamlit App

I am trying build a sharable streamlit app.
My idea is to capture user inputs from (selectbox, multiselectbox, toggle, checkbox etc.) from (top of) the page into URL and use URL back to restore the state.

But the issue here is with circular loop. Changing the selection value is changing the queryparams (on callback) and it inturn tries to update the default value and reruns the page immediately, without giving chance to user to pick more than one option in case of multiselect.

This is not an issue with other widgets which take only one input from user (selectbox, checkbox, toggle etc.), but multiselect suffers as now user can only pick one option at a time.
It also is probably rerunning the app for each input, which is bad.

out

Here is the relevant code to reproduce.

#! /usr/bin/env python3
import streamlit as st


def updateQueryParams(key):
    '''
    Update the query_params with the current session_state
    '''

    print(f'updateQueryParams: {key} {st.session_state[key]}')
    if key in st.session_state and st.session_state[key] is not None:
        st.query_params[key] = st.session_state[key]
    else:
        st.query_params.pop(key, None)


def addPlainMultiSelect(opts):
    '''Add a plain multiselect'''

    st.multiselect('Multiselect', opts, key='multiselect')


def addMultiSelectWithCallback(opts):
    '''Add a multiselect with callback'''

    key = 'multiselect2'

    default = None
    if key in st.query_params:
        default = set(st.query_params.get_all(key)) & set(opts)

    st.multiselect(
        'MultiSelect With Callback',
        opts,
        default=default,
        key=key,
        on_change=updateQueryParams,
        args=[key]
    )


def test():
    '''The main function'''

    opts = [f'Val{i}' for i in range(10)]
    addPlainMultiSelect(opts)
    addMultiSelectWithCallback(opts)



if __name__ == '__main__':
    test()

I am wondering if there is a better way to do this?

WHat I want is - callback should run after full user selection, when user clicks outside the dropdown or presses escape???

st.form might be an option but I want to do without it since it removes the liveliness of the page

You can try storing all the options in a variable instead of on_callback param then use those options to update query

options = st.multiselect(
      'MultiSelect With Callback',
        opts,
        default=default,
        key=key,
        args=[key]
    )

Right but how/when do I populate the URL then?

For that you’ll have to create a button that when pressed will update the query.
That’s the only workaround I can think of rn.

1 Like

After some research in Streamlit documentation, I figured out that updating session_state instead of updating the default value is doing the magic

    if key in st.query_params:
        st.session_state[key] = list(set(st.query_params.get_all(key)) & set(opts))

Here is the full code

#! /usr/bin/env python3
import streamlit as st


def updateQueryParams(key):
    '''
    Update the query_params with the current session_state
    '''

    print(f'updateQueryParams: {key} {st.session_state[key]}')
    if key in st.session_state and st.session_state[key] is not None:
        st.query_params[key] = st.session_state[key]
    else:
        st.query_params.pop(key, None)


def addPlainMultiSelect(opts):
    '''Add a plain multiselect'''

    st.multiselect('Multiselect', opts, key='multiselect')


def addMultiSelectWithCallback(opts):
    '''Add a multiselect with callback'''

    key = 'multiselect2'

    if key in st.query_params:
        st.session_state[key] = list(set(st.query_params.get_all(key)) & set(opts))

    st.multiselect(
        'MultiSelect With Callback',
        opts,
        key=key,
        on_change=updateQueryParams,
        args=[key]
    )


def test():
    '''The main function'''

    opts = [f'Val{i}' for i in range(10)]
    addPlainMultiSelect(opts)
    addMultiSelectWithCallback(opts)



if __name__ == '__main__':
    test()
1 Like

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