Ok, here are two ways: a further clarification of what I was suggesting before, more tailored to your case, and another one based on your further clarification.
First Case
You have a dataframe that starts off empty, each click of the Submit button records a new line into that dataframe (housed safely in session_state
so that it survives). So even though the page reloads with each interaction from the user, it’s grabbing the information from session_state
to cumulatively build the data.
import streamlit as st
import pandas as pd
st.write('# Solution using a dataframe')
# initialize the empty data frame on first page load
if 'data' not in st.session_state:
data = pd.DataFrame({'Expense':[],'Amount':[],'Budget':[],'Variance':[]})
st.session_state.data = data
# show current data (will be empty to first time the page is opened, but will then show the
# incrementally built table of data with each user interactions
st.dataframe(st.session_state.data)
# this is the function the sends the information to that dataframe when called
# variance is calculated at this point
def add_dfForm():
row = pd.DataFrame({'Expense':[st.session_state.input_expense],
'Amount':[st.session_state.input_amount],
'Budget':[st.session_state.input_budget],
'Variance':[st.session_state.input_budget-st.session_state.input_amount]})
st.session_state.data = pd.concat([st.session_state.data, row])
# here's the place for the user to specify information
dfForm = st.form(clear_on_submit=True, key='dfForm')
with dfForm:
dfColumns = st.columns(4)
with dfColumns[0]:
st.text_input('Expense', key='input_expense')
with dfColumns[1]:
st.number_input('Amount', key='input_amount')
with dfColumns[2]:
st.number_input('Budget', key='input_budget')
with dfColumns[3]:
# this button calls the add_dfForm funciton to add data when clicked
# after add_dfForm runs, the page reloads from the top, rerunning and overwriting everything
st.form_submit_button(on_click=add_dfForm)
Second Case
You are on the right track looping through with a counter to create a list of inputs and include a way of making the keys unique. You don’t want those keys to be random though, since each interaction will reload the page, regenerate the random key, and thus create a new, naive widget. (There is a time and place for using a time stamp in a key if you ever need to forcibly destroy and recreate a specific widget. The disabled variance button didn’t seem to have any trouble updating without that extra step in this case though.)
# a selection for the user to specify the number of rows
num_rows = st.slider('Number of Rows', min_value=1,max_value=10,value=1)
# columns to lay out the inputs
grid = st.columns(4)
def add_row(row):
with grid[0]:
st.text_input('Expense', key=f'input_expense{row}')
with grid[1]:
st.number_input('Amount', key=f'input_amount{row}')
with grid[2]:
st.number_input('Budget', key=f'input_budget{row}')
with grid[3]:
st.number_input('Variance', key=f'input_variance{row}',
value = st.session_state[f'input_budget{row}'] \
-st.session_state[f'input_amount{row}'],
disabled=True)
for r in range(num_rows):
add_row(r)