Using st.empty()

Hi, I’m unable to understand the behaviour of st.empty(). In the code below I have used the function to have 3 different page kinds of behaviour, but on the last page, the content of page 2 is getting added. I have tried adding ph.empty() (This caused an error) and ph = st.empty() but still the output is the same. The Code:

def c():
    st.session_state.b1 = True
def d():
    st.session_state.b2 = True
b1 = False
b2 = False
ph = st.empty()
if 'b1' not in st.session_state:
    st.session_state.b1 = False
if 'b2' not in st.session_state:
    st.session_state.b2 = False

with ph.container():
    st.write("Page 1")
    b1 = st.button("Page 2",on_click=c)

if (st.session_state.b1 or b1):
    with ph.container():
        st.write("Page 2")
        st.write("Page 2 contents")
        b2 = st.button("Page 3",on_click=d)

if (st.session_state.b2 or b2) is True:
    with ph.container():
        st.write("Page 3")

Screenshot 2022-08-19 163925
I have found a workaround (i.e add st.write("") at page 3 for each element that is extra). Would like to understand why this happens. Thanks :v:

Hi @VishnuS-S,

The reason this is happening is that when you use with ... with an empty element, you are creating a multi-element container inside of it. And, when you use it the second time, the container isn’t emptied, but instead elements in it are replaced one at a time. You can work around this by calling ph.empty() before populating it. For some reason, you also have to add a small sleep for the empty to actually work. Here’s a slightly modified version of your script that calls an empty function before replacing the container’s contents.

from time import sleep
import streamlit as st

def empty():
    ph.empty()
    sleep(0.01)

def c():
    st.session_state.b1 = True

def d():
    st.session_state.b2 = True

b1 = False
b2 = False
ph = st.empty()
if "b1" not in st.session_state:
    st.session_state.b1 = False
if "b2" not in st.session_state:
    st.session_state.b2 = False

with ph.container():
    st.write("Page 1")
    b1 = st.button("Page 2", on_click=c)

if st.session_state.b1 or b1:
    empty()
    with ph.container():
        st.write("Page 2")
        st.write("Page 2 contents")
        b2 = st.button("Page 3", on_click=d)

if (st.session_state.b2 or b2) is True:
    empty()
    with ph.container():
        st.write("Page 3")
1 Like

Just to overcomplicate expand a bit on that, you could think of ph = st.empty() as initializing an empty list, and of each line within each with ph.container() block as an assignment to the i-th element of ph. Check the comments in the code below:

import streamlit as st 

##  Logic control
def c(): st.session_state.b1 = True
def d(): st.session_state.b2 = True
if 'b1' not in st.session_state: st.session_state.b1 = False
if 'b2' not in st.session_state: st.session_state.b2 = False

## Think of the placeholder here as a list initialized with no elements
ph = st.empty()

## Here you start adding elements to the placeholder 
with ph.container():
    st.write("1️⃣ First element in placeholder")                      # ph[0] = a st.write        
    st.button("1️⃣ Second element in ph (Go to page 2)",on_click=c)   # ph[1] = a st.button

if st.session_state.b1:
    with ph.container():
        st.write("2️⃣ This overwrites the first element")    # ph[0] = a new st.write
        st.write("2️⃣ This overwrites the second element")   # ph[1] = a new st.write
        st.write("2️⃣ Third element in placeholder")         # ph[2] = a st.write
        st.button("2️⃣ Fourth element in ph (Go to page 3)",on_click=d)     # ph[3] = a st.button

if st.session_state.b2:
    with ph.container():
        st.write("3️⃣ This replaces again the first element")   # ph[0] = a new st.write
        st.write("3️⃣ This replaces again the second element")  # ph[1] = a new st.write
        ## Nothing is replacing the third element              # ph[2] = a st.write
        ## Nothing is replacing the fourth element             # ph[3] = a st.button

stempty


imo, that stream of if-statements is not the most readable structure to achieve the intended pages-like behavior :melting_face:. Here’s an idea using if-elif blocks:

pagecounter

import streamlit as st
# Pages logic 
if 'page' not in st.session_state: st.session_state.page = 0
def nextPage(): st.session_state.page += 1
def firstPage(): st.session_state.page = 0

ph = st.empty()

## Page 0
if st.session_state.page == 0:
    with ph.container():
        st.header("This is page 1")
        st.button("Go to page 2",on_click=nextPage)

## Page 1
elif st.session_state.page == 1:
    with ph.container():
        st.header("This is page 2")
        st.write("Other stuff in page 2")
        st.write("More stuff in page 2")
        st.write("More more stuff in page 2")
        st.write("More more more stuff in page 2")
        st.button("Go to page 3",on_click=nextPage)

## Page 2
elif st.session_state.page == 2:
    with ph.container():
        st.header("This is page 3")
        st.image("https://placekitten.com/g/1400/600",caption=f"Meowhy")
        st.button("Go back",on_click=firstPage)
2 Likes

Thanks, @blackary and @edsaac for the details explanations and solutions. This helps a lot for my use case. Thanks again :v:

Can I ask what is the point of this? I came across this topic trying to figure out the use of the containers and it seems to me that your code would work just the same without the containers. What am I not seeing?

Well, OP’s question was about the difference between st.empty, st.container and calling the container method of an st.empty instance.

I guess you are asking about how useful are these placeholders, and you are right, you can achieve the same results without setting placeholders at all. But imo, placeholders improve readability and make code easier to maintain if you separate layout and contents, in case, for instance, you’d like to redesign the page.

An example that can totally be rewritten without placeholders:

placeholders

import streamlit as st

# Pages logic 
if 'page' not in st.session_state: st.session_state.page = 0
def nextPage(): st.session_state.page += 1
def firstPage(): st.session_state.page = 0

############################
# Layout 
############################
"# My title"
ph_description = st.empty()

"## My first section"
ph_dynamic_page = st.empty()
ph_dynamic_cat = st.empty()

"## My second section"
ph_dynamic_img = st.empty()

############################
## Contents
############################

## Description

with ph_description.container():
	st.write("This is some text "*20)

## Second section

with ph_dynamic_img.container():
    if st.session_state.page == 2:
        st.write(f"This only appears with **cat {st.session_state.page}**")
        st.image("https://picsum.photos/400", caption="Not cat")
    else:
        st.write("Nothing to see here 👻")

## First section

with ph_dynamic_page.container():
	st.subheader(f"This is cat {st.session_state.page}")

with ph_dynamic_cat.container():
    cat_size = int((st.session_state.page+1)*50)
    st.image(f"https://placekitten.com/g/{cat_size}",caption=f"Meowhy: {cat_size}")
    st.button(f"Go to cat {st.session_state.page}",
        on_click=(nextPage, firstPage)[st.session_state.page >= 3])
1 Like

Thanks, I see now how this could improve readability in come cases.

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