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")
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
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")
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
imo, that stream of if-statements is not the most readable structure to achieve the intended pages-like behavior . Here’s an idea using if-elif blocks:
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)
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:
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])