How to Use Two @st.dialog Modals Without Conflicts?

We need to use two @st.dialog modals:
:one: Confirmation Dialog – Asks for confirmation before an action.
:two: Success Dialog – Shows details after the action is completed.

Since Streamlit allows only one dialog at a time, the success dialog does not appear after confirming the first one. We track modal states using st.session_state and call st.rerun(), but the second dialog doesn’t always show.

Questions:

  • How can we handle multiple dialogs efficiently?
  • Can we define one dialog in a separate file and call it after the first one closes?
  • Any best practices to avoid session state conflicts?

Would appreciate any suggestions! Thanks. :blush:

" We track modal states using st.session_state and call st.rerun()" looks like the right and efficient thing to do. “[B]ut the second dialog doesn’t always show” hints at a bug in your code, not any inefficiencies.

Yes and yes.

What are session state conflicts?

Anyway, for the case you are describing, you just need to track in session_state the need to show the details. Easy peasy.

@st.dialog(title="Destroy the universe")
def confirm():
    st.write("You are about to destroy the universe.")
    if st.button("Proceed"):
        st.session_state.must_show_details = True
        st.rerun()

@st.dialog(title="Details")
def show_details():
    st.write("The universe has been destroyed.")

if st.button("Destroy the universe"):
    confirm()
if st.session_state.get("must_show_details", False):
    st.session_state.must_show_details = False
    show_details()

Did it work for your code snippet you have sent here?

I tried implementing confirmation and success dialogs in my Streamlit app, but I’m running into this error:

line 69, in _assert_first_dialog_to_be_opened
raise StreamlitAPIException(
streamlit.errors.StreamlitAPIException: Only one dialog is allowed to be opened at the same time. Please make sure to not call a dialog-decorated function more than once in a script run.

The logic I followed:

  • A confirmation dialog asks the user to confirm changes before running a pipeline.
  • If confirmed, the job runs, and a success dialog should appear afterward.
  • The success modal should only open after rerun to avoid conflicts.

Here simplified version of my implementation

def show_confirm_modal():
    """Displays a confirmation modal before execution."""
    st.warning("You have unsaved changes. Do you want to proceed?")
    
    if st.button("Yes, Run Pipeline"):
        st.session_state["show_confirm_modal"] = False
        st.session_state["success_details"] = st.session_state["on_confirm"]()
        st.session_state["open_success_modal_next"] = True  # Flag for success modal
        st.rerun()

    if st.button("Cancel"):
        st.session_state["show_confirm_modal"] = False
        st.session_state["on_cancel"]()
        st.rerun()

@st.dialog("Success")
def show_success_modal(details):
    """Displays a success message after job execution."""
    st.markdown(f"Pipeline executed successfully: {details.get('pipeline_name', 'N/A')}")
    
    if st.button("Close"):
        st.session_state["dialog_type"] = None
        st.rerun()

if run_job:
    if changes_detected:
        st.session_state["show_confirm_modal"] = True
        st.rerun()
    else:
        st.session_state["success_details"] = self._execute_job(pipeline_name)
        st.session_state["show_success_modal"] = True
        st.rerun()

# Open modals at the right time
if st.session_state.get("show_confirm_modal", False):
    show_confirm_modal()

if st.session_state.get("open_success_modal_next", False):
    show_success_modal(st.session_state.get("success_details", {}))
    st.session_state["open_success_modal_next"] = False  # Reset flag

Issue:

  • The success dialog does show up after confirming the pipeline execution but with error I posted above
  • The error occurs when both dialogs seem to be triggered in the same script run.
  • Since Streamlit reruns the script from top to bottom, how can I properly handle the transition between these two dialogs without violating the “one dialog at a time” rule?

Didn’t it work for you?

That code references quite a few missing parts, but when I implemented them in the simplest possible way, it worked without issues.

Make sure the conditions that trigger the dialogs are not both true in the same rerun.

Yes its working when using your code without any issue

Could you please pointed out where I am making wrong or did I miss something in my code?
Thanks in advance

My whole implementation here.

if trigger_job:
    stored_values = fetch_values_from_db(job_name)
    has_changes = compare_inputs(stored_values, user_inputs)

    if has_changes:
        st.session_state.update({
            "confirm_job_execution": job_name,
            "pending_changes": {k: (stored_values.get(k, ""), v) for k, v in user_inputs.items() if str(v) != str(stored_values.get(k, ""))},
            "execute_job": lambda: run_job(job_name),
            "show_confirmation": True
        })
    else:
        st.session_state.update({
            "execution_details": run_job(job_name),
            "display_success": True
        })
    st.rerun()

if st.session_state.get("show_confirmation"):
    display_confirmation()
elif st.session_state.get("display_success"):
    display_success()

@st.dialog("⚠️ Confirm Pending Changes")
def display_confirmation():
    job_name = st.session_state.get("confirm_job_execution", "Unknown Job")
    pending_changes = st.session_state.get("pending_changes", {})
    
    st.markdown(f"### 🚨 Changes Detected in `{job_name}`")
    for key, (old, new) in pending_changes.items():
        st.markdown(f"**{key}:** 🔴 ~~{old}~~ → 🟢 **{new}**")
    
    if st.button("✅ Yes, Execute Job"):
        st.session_state.update({
            "show_confirmation": False,
            "execution_details": st.session_state["execute_job"](),
            "display_success": True
        })
        st.rerun()
    if st.button("❌ No, Cancel"):
        st.session_state["show_confirmation"] = False
        st.rerun()

@st.dialog("✅ Job Executed Successfully")
def display_success():
    details = st.session_state.get("execution_details", {})
    st.markdown(f"**Job:** {details.get('job_name', 'N/A')}  ")
    st.markdown(f"[View Job]({details.get('job_link', '#')})  ")
    st.markdown(f"**Timestamp:** {details.get('timestamp', 'Unknown')}")
    
    if st.button("Close"):
        st.session_state["display_success"] = False
        st.rerun()

Your whole implementation has even more undefined names. After I defined them in the simplest possible way, I found that, sometimes, the success dialog is shown after you close the confirmation dialog using the cancel button. This happens because you do not set st.session_state.display_success to False.

I couldn’t trigger any “Only one dialog is allowed to be opened at the same time” errors.

I made changes to work with two dialogs but still need to use st.stop() to prevent both dialogs from running at the same time.

If I use st.rerun() for st.session_state.get("confirm_changes_popup", False), the page refreshes automatically.

if st.session_state.get("confirm_changes_popup", False):
    display_confirmation_dialog()
    st.stop()

if st.session_state.get("display_success_modal", False):
    st.session_state["display_success_modal"] = False
    display_success_dialog()

I have implemented the full logic, but with your code snippet, everything works perfectly without needing st.rerun() or st.stop().

Could you please help me understand what I need to take care of?

@st.dialog(title="⚠️ Confirm Unsaved Changes")
def display_confirmation_dialog():
    pipeline_name = st.session_state.get("pending_pipeline_name", "Unknown Pipeline")
    unsaved_changes = st.session_state.get("pending_changes", {})
    st.warning("You have unsaved changes. Do you want to proceed without submitting?")
    col1, col2 = st.columns(2)

    if col1.button("✅ Yes, Run Pipeline"):
        st.session_state["confirm_changes_popup"] = False
        st.session_state["execution_result"] = st.session_state["confirm_action"]()
        st.session_state["display_success_modal"] = True
        st.rerun()

    if col2.button("❌ No, Cancel"):
        st.session_state["confirm_changes_popup"] = False
        st.session_state["cancel_action"]()
        st.rerun()


@st.dialog(title="✅ Job Executed Successfully")
def display_success_dialog():
    details = st.session_state.get("execution_result", {})
    if st.button("Close"):
        st.session_state["display_success_modal"] = False
        st.rerun()


# "Run Now" Logic
if run_job:  # Button Click
    current_values = self.fetch_current_values_from_db(pipeline_name)
    changes_detected = self.compare_values(current_values, user_inputs)

    if changes_detected:
        st.session_state["pending_pipeline_name"] = pipeline_name  
        st.session_state["pending_changes"] = {
            k: (current_values.get(k, ""), v)
            for k, v in user_inputs.items()
            if str(v) != str(current_values.get(k, ""))
        }

        # Define confirmation and cancel actions
        st.session_state["confirm_action"] = lambda: self._execute_job(pipeline_name)
        st.session_state["cancel_action"] = lambda: None  
        st.session_state["confirm_changes_popup"] = True
        st.rerun()
    else:
        details = self._execute_job(pipeline_name)
        if details:
            st.session_state["execution_result"] = details
            st.session_state["display_success_modal"] = True
            st.rerun()

# Display confirmation and success dialogs if needed
if st.session_state.get("confirm_changes_popup", False):
    display_confirmation_dialog()
    st.stop()

if st.session_state.get("display_success_modal", False):
    st.session_state["display_success_modal"] = False
    display_success_dialog()

The code seems to be working now (again, after filling in the missing parts). What is what you don’t understand?

From the confirmation dialog (show_confirm_modal()), when we click Yes, I set the st.session_state.must_show_success = Trueand do the rerun to run the script
To call the success dialog which is my 2nd dialog, calling via below snippet.

if st.session_state.get("must_show_success", False):
    st.session_state.must_show_success = False
    show_success_modal()

So as per the logic seems to fine but when I run the script. I am getting the error of one dialog should be run in a script rule

The code seems to be working now (again, after filling in the missing parts).

What are the missing parts you mentioned is not understandable.

Instead of managing 1st dialog via session state, I directly called it inside the run now when its invoked. Now it seems to be working.

st.session_state["on_confirm"] = lambda: self._execute_job(pipeline_name)
st.session_state["on_cancel"] = lambda:st.session_state.update({"must_show_success": False})
 display_confirmation_dialog()# Direct call to confirm dialog to avoid rerun

Thanks @Goyo for your time and insights on here.

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