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