How does st.fragment work with input arguments?

Is a function with the @st.fragment decorator supposed to re-run when its input arguments change?

I am trying to create a component with two columns. The left column has a series of buttons and the right renders data based on which button was clicked. I want this component to operate independently of the rest of the app. When a button on the left is clicked, only the right pane should run, not the rest of the app.

I thought the st.fragment decorator around this component could accomplish this, however it seems to cause the function to not respond properly to changes in its input arguments. Below is a MWE. (For context, this is the skeleton of a document retrieval dashboard which will have a series of document summaries on the left, each associated with a button. When the button for a document is clicked, the app will show a detailed analysis of the document on the right.)

import streamlit as st
import time

@st.fragment  # <-- seems to cause function to retain initial value of n
def my_component(n):
    st.write(f"My component with {n} buttons")
    col_1, col_2 = st.columns([1,1])
    with col_1:
        # array of buttons
        for i in range(n):
            st.button(f"Button {i}", on_click=lambda x: col_2.write(f"Button {x} clicked"), args=[i])

with st.spinner():
    time.sleep(2)
    st.write("Long operation completed...")
    st.divider()

n = st.number_input("Number of Buttons", min_value=1, max_value=10, value=4)
my_component(n)

When I start the app, there are 4 buttons on the left hand side, clicking any one renders the text on the right pane. When I change the input number to 10 (akin to changing query in document retrieval system) I do initially get a new set of buttons (so it looks like the my_component function is rerunning when n changes). However, when I click one of these buttons, the function reverts to its behavior on its initial argument set with n=4 (only 4 buttons are rendered - the others disappear).

How is st.fragment decorator supposed to work when the function has input arguments? I noticed that all examples in the documentation have no input arguments.

If I build a workaround adhering to this constraint (that the decorated function should have no input arguments), it does work as expected:

  • Each time I change n, the correct number of buttons are rendered in the component
  • When I click a button it does not revert to the initial state of n=4
  • The rest of the app (with sleep timer) is not re-rendered.
import streamlit as st
import time

# @st.fragment  # <-- seems to retain initial value of n
def my_component(n):
    st.write(f"My component with {n} buttons")
    col_1, col_2 = st.columns([1,1])
    with col_1:
        # array of buttons
        for i in range(n):
            st.button(f"Button {i}", on_click=lambda x: col_2.write(f"Button {x} clicked"), args=[i])

with st.spinner():
    time.sleep(2)
    st.write("Long operation completed...")
    st.divider()

# Wrap the component and argument selection such that the
# function with @st.fragment decorator has no input arguments
@st.fragment
def my_component_with_query():
    n = st.number_input("Number of Buttons", min_value=1, max_value=10, value=4)
    my_component(n)

my_component_with_query()

I am running locally with Streamlit v1.37.0 with Python v3.12

1 Like

Quite important issue. I experience a similiar behaviour when working with a st.fragment function that has parameters.

I have several containers with serveral widgets on a page. All containers are rendered via a function with st.fragment decorator.
The function also uses a parameter.

And clicking on a widget inside a fragement causes strange behaviour. Despite the page is rendered entirely and the appearance is correct, the click causes that the appearance of the content inside the widget is suddenly as it was before the new page rendering.

1 Like