Invoking another st.button

Summary

I am working on to display Movie recommendations; I am able to display 10 movies when a user input from st.selectbox and click on “Show Recommendation” Button.
I am also able to get the button on caption of each movie but when i click on the button for one of the movies, it is not calling the function again.
It is going to the first page of “Show Recommendation” with st.selectbox showing the movie name for which i got the 10 movies.

When user click on the movie it shld display the next 10 movie related to that movie.

Steps to reproduce

Code snippet:

if st.button('Show Recommendations'):
  
    movie_name(selected_movie_name)


def movie_name(selected_movie_name)
  with col1:
            # st.text(recommended_movies[0])
            st.image(recommended_movie_posters[0])
            selected_movie_name=recommended_movies[0]
            button=st.button(recommended_movies[0])
            if button:
                movie_name(selected_movie_name)

button value is coming as “False”

Buttons don’t have a persistent state. They will return True immediately after clicking them, but go back to False if the user interacts with anything else. So upon clicking a button within your movie_name function, your page will reload with st.button('Show Recommendations') being false and it will look like the first page load.

You can either use a checkbox so that there is some retained state, or you can use session state to keep a memory of your button clicks.

Here’s an example creating some memory for the outer button. You can do the same for more nested buttons as needed.

# Initialize some state for showing recommendations
if 'show_recommendation' not in st.session_state:
    st.session_state.show_recommendation = False

# Callback function to make sure the state changes with each button click
def change_show():
    st.session_state.show_recommendation = not st.session_state.show_recommendation

# Independent button, with attached callback function
st.button('Show Recommendations', on_click=change_show)

# Conditional called on the key stored in session state instead of directly on the button value
if st.session_state.show_recommendation:
    st.write('Movie recommendations showing here')
    st.button('Another button here')

Here’s some helpful links:

Still not working:
Here is my code:

def movie_name(selected_movie_name):

if (selected_movie_name != '<select>'):

    recommendations = recommend(selected_movie_name)
    recommended_movies = []
    recommended_movie_posters = []
    for index, row in recommendations.iterrows():
        title = row['Recommended Movies']
        movie_id = row['movie_id']
        recommended_movies.append(title)
        recommended_movie_posters.append(fetch_poster(movie_id))

    col1, col2, col3, col4, col5 = st.columns(5)
    with col1:
        # st.text(recommended_movies[0])
        st.image(recommended_movie_posters[0])
        submitted = st.button(recommended_movies[0], on_click=set_stage, args=(2,))

############# I want to do for col 2, 3,4, 5 as well

def set_stage(stage):
st.session_state.stage = stage

if “stage” not in st.session_state:
st.session_state.stage = 0

if(st.button(‘Show Recommendations’, on_click=set_stage, args=(1,))):
recommended_movies=movie_name(selected_movie_name)

if st.session_state[“stage”] == 2:
movie_name(recommended_movies[0])

getting error : "NameError: name ‘recommended_movies’ is not defined

getting error for second button:
but now it is keeping the first button display

If you want any persistence with the effect of a button, make sure you are not passing the button directly into your conditional. The point of the callback function is to store information in session state that will survive past what the button remembers. You want to base your conditionals on that session state information.

So instead of:

Set the button outside of the conditional with the callback to record info in session state, then set your conditional on that session state value:

st.button(‘Show Recommendations’, on_click=set_stage, args=(1,))

if st.session_state.stage > 0:
    recommended_movies=movie_name(selected_movie_name)

Thanks!!!
But now…it is trying to display both from “first button and then second button” because of which it is throwing key error " DuplicateWidgetID : There are multiple identical st.button widgets with key='11' ."

Initially is should show first 10 movies…then if I click on one of the movies…the first 10 should clear up and then next 10 corresponding to the movie I clicked should show up.

Thanks for the help!!!

def set_stage(stage):
st.session_state.stage = stage
global movie1

if “stage” not in st.session_state:
st.session_state.stage = 0

st.button(‘Show Recommendations’, on_click=set_stage, args=(1,))
if st.session_state.stage > 0:
recommended_movies=movie_name(selected_movie_name)
movie1=recommended_movies[0]
st.write(movie1)

if st.session_state[“stage”] == 2:
st.write(“Hello”)
st.write(movie1)
movie_name(movie1)

If you use the code blocking to preserve white space, it will be a bit easier to parse what your code says. :slight_smile: Like this:

def my_func():
    st.write('Hello')

image

If you have code that is going to generate a button (or any widget) within a loop, you will need to manually assign a unique key to that widget so there is no conflict.

for movie in movie_list:
    st.button('Show', key=movie)

If you have multiple widgets being created per movie, expand the string used for the key so that it is truly unique across all widgets within all loops, etc.

Thanks!!!

But the issue is …it is displaying for both the button.
I need 10 movies at a time from only 1 button.

1 Like

first row in the attached screenshot is from the first button…2nd 5 movies…
and 2nd display is failing

I’m a little unclear here. Do you have your full script in a repository you could link? Or could you show a bit more of where you are right now in code?

Here is the link to the code:

1 Like

Ok, I see what you tried to do. Assuming you want the “Show Recommendations” button to present the 10 movies, then from there click any of the movies button shows some additional information for that movie:

You would probably be best served having two distinct keys in session state to separate the concepts of “where you are in the process” and “what option among many was selected.” I don’t know if you have any further buttons beyond the two steps shown, but you can create a different key to register the last button clicked among the recommendations. If you need to nest more layers of buttons, you can adjust callback functions to update the stage key also.

You can condense down where you are listing out the 10 recommendations with their buttons like this:

# New callback for the individual movie buttons
def selected(i):
    st.session_state.selected = i

# initialize a new key
if 'selected' not in st.session_state:
    st.session_state.selected = None

# get your columns as a list so it's easier to increment through them
columns = st.columns(5)
# increment through your 10 items in the list, by index
for i in range(10):
    # increment through your columns by index, using mod 5 to loop through 0 to 4, twice
    with columns[i%5]:
        # st.text(recommended_movies[i])
        st.image(recommended_movie_posters[i])
        # now the individual movie buttons affect the 'selected' key instead of the 'stage' key
        st.button(recommended_movies[i], on_click=selected, args=(i,), key=recommended_movies[i])

if st.session_state.selected is not None:
    i = st.session_state.selected
    st.write(recommended_movies[i])
    movie_name(recommended_movies[i])

If you need each movie button to work like an on/off switch and potentially show more than one secondary result at once, that’s possible too but would have a different structure to save more than a single index number at a time.

I have this one :frowning: but sitll some issues

def set_stage(stage):
st.session_state.stage = stage

New callback for the individual movie buttons

def selected(i):
st.session_state.selected = i

ef movie_name(selected_movie_name):
if (selected_movie_name != ‘

Want to know how to reset the key, since the same movie can comes for other recommendations movies.
Also everytime it should just show 10 movies only

You can append an extra digit or string to your widget keys representing the tier they are displayed in to keep the keys unique. For example, the first 10 would have recommended_movies[i]+'first' as the key and when you are showing them in the next tier, you would use recommended_movies[i]+'second'. But if you are going to nest these expansions of 10 movies at a time further, you will either need additional keys or you’ll need to make your “selection” a list so it can keep track of a series of selections.

You can create buttons with new callback functions to reset whichever keys you want for your process.

def clear():
    st.session_state.selected = None

def restart():
    st.session_state.stage = 0
    clear()

st.button('Start Over', on_click=restart)
st.button('Clear', on_click=clear)