icon: LiNotebookTitle: Streamlit - A Deep Dive
Now that you have experience with the basics of Streamlit, we will look into some important concepts of Streamlit that have caught some developers off guard and led to unexpected behavior in their applications.
Understanding these concepts is crucial for designing and developing efficient and effective Streamlit apps. In this deep dive, we will explore session state, button behavior, and dynamic widget management, providing you with the knowledge to avoid common pitfalls and enhance your app's functionality.
By having a solid understanding of these elements, you will be better equipped to create user-friendly applications and make the most of Streamlit's features in your projects.
This note provides a concise introduction to key "advanced" concepts in Streamlit, aiming to familiarize you with the framework's mechanics and empower you to begin building apps quickly. That's the best way to understand the concepts is to open up your VS Code and try to experiment with the code.
However, it's essential to understand that this is not an exhaustive resource. The official Streamlit documentation remains the definitive guide, offering comprehensive explanations, advanced techniques, and best practices.
To ensure a thorough understanding of Streamlit, we strongly recommend reading the official documentation. We will be including the links to the corresponding section in the official documentation in the different parts of the note below.
We recommend that you first try working with the content in this note. This should be sufficient to get you started. Remember to revisit this note for the links to the official documentation after you have gained some hands-on experience or when you encounter challenges implementing certain things. This approach is more effective.
Streamlit defines access to a Streamlit app in a browser tab as a session.
A session is a single instance of viewing an app. If you view an app from two different tabs in your browser, each tab will have its own session. So each viewer of an app will have a Session State tied to their specific view. Streamlit maintains this session as the user interacts with the app. If the user refreshes their browser page or reloads the URL to the app, their Session State resets and they begin again with a new session.
Session State is a way to share variables between reruns, for each user session. In addition to the ability to store and persist state, Streamlit also exposes the ability to manipulate state using Callbacks. Session state also persists across apps inside a multipage app.
Check out this video by Streamlit Developer Advocate Dr. Marisa Smith to on two very essential concepts for Streamlit app development:
session statecallbackSession State provides a dictionary-like interface where you can save information that is preserved between script reruns. Use st.session_state with key or attribute notation to store and recall values. For example, st.session_state["my_key"] or st.session_state.my_key. Remember that widgets handle their statefulness all by themselves, so you won't always need to use Session State!
There are a few common scenarios where Session State is helpful. As demonstrated in the video above, Session State is used when you have a progressive process that you want to build upon from one rerun to the next. Session State can also be used to prevent recalculation, similar to caching. However, the differences are important:
st.buttonst.button do not retain state. They return True on the script rerun resulting from their click and immediately return to Falseon the next script rerun. 
if st.button('Click me'):False.This note explains the use of buttons and explain common misconceptions.
if st.button()When code is conditioned on a button's value, it will execute once in response to the button being clicked and not again (until the button is clicked again).
✅ Good to nest inside buttons:
❌ Bad to nest inside buttons:
If you want to give the user a quick button to check if an entry is valid, but not keep that check displayed as the user continues.
animal string is in the animal_shelter list. st.text_input, the script reruns and the message disappears until they click "Check availability" again.import streamlit as st 
animal_shelter = ['cat', 'dog', 'rabbit', 'bird'] 
animal = st.text_input('Type an animal') 
if st.button('Check availability'): 
	have_it = animal.lower() in animal_shelter 
	'We have that animal!' if have_it else 'We don\'t have that animal.'
If you want a clicked button to continue to be True, create a value in st.session_state and use the button to set that value to True in a callback.
import streamlit as st 
if 'clicked' not in st.session_state: 
	st.session_state.clicked = False 
	
def click_button(): 
	st.session_state.clicked = True 
	
st.button('Click me', on_click=click_button) 
if st.session_state.clicked: 
	# The message and nested widget will remain on the page 
	st.write('Button clicked!') 
	st.slider('Select a value')
st.session_stateIf you modify st.session_state inside of a button, you must consider where that button is within the script.
In this example, we access st.session_state.name both before and after the buttons which modify it. When a button ("Jane" or "John") is clicked, the script reruns. The info displayed before the buttons lags behind the info written after the button. The data in st.session_state before the button is not updated. When the script executes the button function, that is when the conditional code to update st.session_state creates the change. Thus, this change is reflected after the button.
import streamlit as st 
import pandas as pd 
if 'name' not in st.session_state: 
	st.session_state['name'] = 'John Doe' 
	
st.header(st.session_state['name']) 
if st.button('Jane'): 
	st.session_state['name'] = 'Jane Doe' 
	
if st.button('John'): 
	st.session_state['name'] = 'John Doe' 
	
st.header(st.session_state['name'])
Callbacks are a clean way to modify st.session_state. Callbacks are executed as a prefix to the script rerunning, so the position of the button relative to accessing data is not important.
import streamlit as st 
import pandas as pd 
if 'name' not in st.session_state: 
	st.session_state['name'] = 'John Doe' 
	
def change_name(name): 
	st.session_state['name'] = name 
	
st.header(st.session_state['name']) 
st.button('Jane', on_click=change_name, args=['Jane Doe']) 
st.button('John', on_click=change_name, args=['John Doe']) 
st.header(st.session_state['name'])
When a button is used to modify or reset another widget, it is the same as the above examples to modify st.session_state. However, an extra consideration exists: you cannot modify a key-value pair in st.session_state if the widget with that key has already been rendered on the page for the current script run.
import streamlit as st 
st.text_input('Name', key='name') 
# These buttons will error because their nested code changes 
# a widget's state after that widget within the script. 
if st.button('Clear name'): 
	st.session_state.name = '' 
	
if st.button('Streamlit!'): 
	st.session_state.name = ('Streamlit')
If you assign a key to a button, you can condition code on a button's state by using its value in st.session_state. This means that logic depending on your button can be in your script before that button. 
In the following example, we use the .get() method on st.session_state because the keys for the buttons will not exist when the script runs for the first time. The .get() method will return False if it can't find the key. Otherwise, it will return the value of the key.
import streamlit as st 
# Use the get method since the keys won't be in session_state 
# on the first script run 
if st.session_state.get('clear'): 
	st.session_state['name'] = '' 
	
if st.session_state.get('streamlit'): 
	st.session_state['name'] = 'Streamlit' 
	
st.text_input('Name', key='name') 
st.button('Clear name', key='clear') 
st.button('Streamlit!', key='streamlit')
import streamlit as st 
st.text_input('Name', key='name') 
def set_name(name): 
	st.session_state.name = name 
	
st.button('Clear name', on_click=set_name, args=['']) 
st.button('Streamlit!', on_click=set_name, args=['Streamlit'])`
By using st.container you can have widgets appear in different orders in your script and frontend view (webpage).
import streamlit as st 
begin = st.container() 
if st.button('Clear name'): 
	st.session_state.name = '' 
	
if st.button('Streamlit!'): 
	st.session_state.name = ('Streamlit') 
	
# The widget is second in logic, but first in display 
begin.text_input('Name', key='name')
Storing unencrypted secrets in a git repository is a bad practice. For applications that require access to sensitive credentials, the recommended solution is to store those credentials outside the repository - such as using a credentials file not committed to the repository or passing them as environment variables.
Streamlit provides native file-based secrets management to easily store and securely access your secrets in your Streamlit app.
Create a file secrets.toml in the $CWD/.streamlit folder  where $CWD is the folder you're running Streamlit from. For example,  $CWD/.streamlit/secrets.toml. 
# Everything in this section will be available as an environment variable 
OPENAPI_API_KEY = "sk-1241infjsenfuhnh81n8cdn"
# You can also add other sections if you like. 
# The contents of sections as shown below will not become environment variables, # but they'll be easily accessible from within Streamlit anyway as we show 
# later. 
[my_other_secrets] 
things_i_like = ["Streamlit", "Python"]
.gitignore so you don't upload your secrets to online repositoryAccess your secrets by querying the st.secrets dict, or as environment variables. For example, if you enter the secrets from the section above, the code below shows you how to access them within your Streamlit app
import streamlit as st 
# Everything is accessible via the st.secrets dict:
st.write("OPENAPI_API_KEY:", st.secrets["OPENAPI_API_KEY"]) 
# Store the value in "Environment Variable"
# This is required by some packages like LangChain
os.environ['OPENAPI_API_KEY'] = st.secrets['OPENAPI_API_KEY']