How do streamlit buttons work on click? [bug]

I am trying to authenticate a user using a simple form an AWS cognito. So far I managed to create the user correctly in cognito and the user is sent by email a code to confirm. The form prompt the user to enter the code but the behaviour is not what I was expecting.

I was expecting the user to enter the code, click the button โ€œConfirmโ€ and the on_click_button() function to be triggered. Instead, I see the function triggered BEFORE the button is clicked, at the point the confirm entry box appears. This is very strange.

I suspect I am failing to understand some aspect how streamlit buttons work. Can somebody help out? My cognito user details are in the code so you can reproduce the error.


import streamlit as st
import boto3
import hashlib
import hmac
import base64

st.set_page_config(page_title="Pixar Star", page_icon="โญ", layout="centered", initial_sidebar_state="collapsed")
# show_pages_from_config()

# Set your Cognito pool id, app client id, and region
POOL_ID = "eu-west-1_WdFlA4Rm6"
APP_CLIENT_ID = "7l2hl2qjicg992bl39gt7lfdk1"
APP_CLIENT_SECRET = "3lbq6m9j87uqn68t7elj4urbo75m1gjp70qu2i2gcdoe3hpu888"
REGION_NAME = "eu-west-1"

# Initialize the Cognito client
client = boto3.client('cognito-idp', region_name=REGION_NAME)

# Initialization state variables
if 'email' not in st.session_state:
    st.session_state['email'] = ''
if 'secret_hash' not in st.session_state:
    st.session_state['secret_hash'] = ''

# Create a Streamlit sign-up form
st.title("Sign Up")
email = st.text_input("Email").lower()
password = st.text_input("Password", type="password")
confirm_password = st.text_input("Confirm Password", type="password")
submit_button = st.button("Sign Up")

# Calculate SECRET_HASH
message = email + APP_CLIENT_ID
key = APP_CLIENT_SECRET.encode('utf-8')
msg = message.encode('utf-8')
SECRET_HASH = base64.b64encode(hmac.new(key, msg, digestmod=hashlib.sha256).digest()).decode()
st.session_state['secret_hash'] = SECRET_HASH

def on_button_click(confirm_code):
    st.write("Confirm Button clicked!")
    st.write(st.session_state['email'])
    try:
        response = client.confirm_sign_up(
            ClientId=APP_CLIENT_ID,
            Username=st.session_state['email'],
            ConfirmationCode=confirm_code,
            SecretHash=st.session_state['secret_hash']
        )
        st.info(response)
        if response['ResponseMetadata']['HTTPStatusCode'] == 200:
            st.success("User confirmed successfully!")
    except Exception as e:
        st.error("An error occurred during confirmation.")
        st.error(str(e))

if submit_button:
    if password != confirm_password:
        st.error("Passwords do not match")
    else:
        st.session_state['email'] = email
        st.session_state['password'] = password
        try:
            response = client.sign_up(
            ClientId=APP_CLIENT_ID,
            Username=st.session_state['email'],
            Password=st.session_state['password'],
            SecretHash=SECRET_HASH,
        )
            if response['ResponseMetadata']['HTTPStatusCode'] == 200:
                st.info("Please check your email for the confirmation code.")
                confirm_code = st.text_input("Confirmation Code")
                print(confirm_code)
                confirm_button = st.button("Confirm", on_click=on_button_click(confirm_code))
        except Exception as e:
            st.error("An error occurred during sign-up.")
            st.error(str(e))

UI view

User enters sign up details and clicks on the first button (Sign up)

the user is created on cognito and asks for confirmation, meanwhile the user also has gotten the email



Until here everything works as I would have expected but look at what has appeared on the UI

The code has not been entered, the button has not been clicked AND yet the function on_click_button has been triggered (and it obviously throws an error because it is not getting the code)

Thanks a lot

Apparently nested buttons are an anti-pattern. I untested it, and it just worked.

I am not fully satisfied because I liked the flow of the confirmation button appearing only after the other one - now both buttons are present from the very beginning - feels a bit basic

import streamlit as st
import boto3
import hashlib
import hmac
import base64

st.set_page_config(page_title="Pixar Star", page_icon="โญ", layout="centered", initial_sidebar_state="collapsed")
# show_pages_from_config()

# Set your Cognito pool id, app client id, and region
POOL_ID = "eu-west-1_WdFlA4Rm6"
APP_CLIENT_ID = "7l2hl2qjicg992bl39gt7lfdk1"
APP_CLIENT_SECRET = "3lbq6m9j87uqn68t7elj4urbo75m1gjp70qu2i2gcdoe3hpu888"
REGION_NAME = "eu-west-1"

# Initialize the Cognito client
client = boto3.client('cognito-idp', region_name=REGION_NAME)

# Initialization state variables
if 'email' not in st.session_state:
    st.session_state['email'] = ''
if 'secret_hash' not in st.session_state:
    st.session_state['secret_hash'] = ''
if 'confirm_code' not in st.session_state:
    st.session_state['confirm_code'] = ''

# Create a Streamlit sign-up form
st.title("Sign Up")
email = st.text_input("Email").lower()
password = st.text_input("Password", type="password")
confirm_password = st.text_input("Confirm Password", type="password")
submit_button = st.button("Sign Up")

# Calculate SECRET_HASH
message = email + APP_CLIENT_ID
key = APP_CLIENT_SECRET.encode('utf-8')
msg = message.encode('utf-8')
SECRET_HASH = base64.b64encode(hmac.new(key, msg, digestmod=hashlib.sha256).digest()).decode()
st.session_state['secret_hash'] = SECRET_HASH

if submit_button:
    if password != confirm_password:
        st.error("Passwords do not match")
    else:
        st.session_state['email'] = email
        try:
            response = client.sign_up(
                ClientId=APP_CLIENT_ID,
                Username=st.session_state['email'],
                Password=password,
                SecretHash=SECRET_HASH,
            )
            if response['ResponseMetadata']['HTTPStatusCode'] == 200:
                st.info("Please check your email for the confirmation code.")
        except Exception as e:
            st.error("An error occurred during sign-up.")
            st.error(str(e))

confirm_code = st.text_input("Confirmation Code")
confirm_button = st.button("Confirm")
st.session_state['confirm_code'] = confirm_code
if confirm_button:
    st.write("Confirm Button clicked!")
    st.write(st.session_state['confirm_code'])
    st.write(st.session_state['email'])
    try:
        response = client.confirm_sign_up(
            ClientId=APP_CLIENT_ID,
            Username=st.session_state['email'],
            ConfirmationCode=st.session_state['confirm_code'],
            SecretHash=st.session_state['secret_hash']
        )
        st.info(response)
        if response['ResponseMetadata']['HTTPStatusCode'] == 200:
            st.success("User confirmed successfully!")
    except Exception as e:
        st.error("An error occurred during confirmation.")
        st.error(str(e))

This will call on_button_click(confirm_code) when it is executed, not when you click the button. Please read the documentation carefully and pay attention at the expected types of the arguments:

on_click (callable) An optional callback invoked when this button is clicked.
args (tuple) An optional tuple of args to pass to the callback.
kwargs (dict) An optional dict of kwargs to pass to the callback.

You probably want something like this instead:

confirm_button = st.button(
    label="Confirm",
    on_click=on_button_click,
    kwargs={"confirm_code": confirm_code}
)

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