Session_state refresh on top of page after change in data_editor

I have a dataframe in data_editor with a checkbox column. And I want to display the average age above the data_editor table in 2 metrics (checked rows and average age).
If I show the metrics below the data_editor, then the correct value is shown. The metrics above the data_editor are not refreshed (they are one click behind).

How can I refresh the metrics above the data_editor showing the correct metrics (the same values as the metrics below the data_editor)?

code:

import streamlit as st
from streamlit import session_state as ss
import pandas as pd

if 'checked_rows' not in ss:
    ss.checked_rows = 0
if 'average_age' not in ss:
    ss.average_age = 0

#metrics above data_editor
st.metric(value=ss.checked_rows, label = "checked rows")
st.metric(value=ss.average_age, label = "average age checked rows")

# data sample
data = {
    'Name': ['John', 'Anna', 'Peter', 'Linda'],
    'Age': [28, 24, 35, 32],
    'Year': [2034, 2023, 2023, 2014]
    }

df = pd.DataFrame(data)
#insert checkbox column
df.insert(0, "check", False)
de = st.data_editor(df, key='data')
checked_rows= de[de['check']]

#checked rows
ss.checked_rows = len(checked_rows)

#average year
if len(checked_rows) != 0:
    avg_age = checked_rows.agg(Age=('Age', 'average')).reset_index()
    ss.average_age = avg_age['Age'][0]

    #metrics below data_editor
st.metric(value=ss.checked_rows, label = "checked rows")
st.metric(value=ss.average_age, label = "average age checked rows")

I ran into this issue with my app as well. The two solutions I went with were 1) Put the display after the check boxes, 2) rerun the page with a submit or update button. I know a lot of the community is requesting the ability to update certain portions of the page without reruning the entire script I would look into st.fragments and modal dialogs.

There may be other options but you can add an on_change argument to your data_editor. See the section Use Callbacks to update Session State. It would be something like

def on_change_function():
    ss.checked_rows = len(checked_rows)

de = st.data_editor(df, key = 'checked_rows', on_change = on_change_function)

My go-to method is usually a callback as @S11 recommended. That will update the value before the script runs so it will be correct from the start of the page load.

Alternatively, a standard technique in Streamlit is to use containers to change the order of elements on the screen.

  1. Create a container before your dataframe
  2. After your dataframe, calculate your values.
  3. Then, call st.metric and render it into the container above your data editor.

I don’t know your whole use-case, but just for reference, if you’re using the data editor just for row selections, version 1.35.0 of Streamlit does have this capability for st.dataframe and that would open up a third option: just get the selections directly from Session State from the dataframe’s key. This is also possible with st.data_editor, but is considerably more messy.

thanks for your reply, but
this is not working, still the same behavior when I add the callback function. Maybe this is not working because of the data editor.

code with the callback:

import streamlit as st
from streamlit import session_state as ss
import pandas as pd

def on_change_function():
    ss.checked_rows = len(checked_rows)

if 'checked_rows' not in ss:
    ss.checked_rows = 0
if 'average_age' not in ss:
    ss.average_age = 0

#metrics above data_editor
st.metric(value=ss.checked_rows, label = "checked rows")
st.metric(value=ss.average_age, label = "average age checked rows")

# data sample
data = {
    'Name': ['John', 'Anna', 'Peter', 'Linda'],
    'Age': [28, 24, 35, 32],
    'Year': [2034, 2023, 2023, 2014]
    }

df = pd.DataFrame(data)
#insert checkbox column
df.insert(0, "check", False)
de = st.data_editor(df, key = 'data', on_change = on_change_function)
checked_rows= de[de['check']]

#checked rows
ss.checked_rows = len(checked_rows)

#average year
if len(checked_rows) != 0:
    avg_age = checked_rows.agg(Age=('Age', 'average')).reset_index()
    ss.average_age = avg_age['Age'][0]

    #metrics below data_editor
st.metric(value=ss.checked_rows, label = "checked rows")
st.metric(value=ss.average_age, label = "average age checked rows")```

Thanks for your reply.

In the use case I have an update button, but it’s less user friendly that the user has to click a button (and it is hard to tell if the metrics are in sync with the detail data). So therefor I want to update the metric automatically. But if it is not working, I will go with your solution!

Your st.metric is missing a key argument which is the same as the st.session_state variable. In this case, you need

st.metric(key = checked_rows, label = 'checked rows')

Since you are adding checked_rows to the SessionState before your metric (in the if statement), the initial value will be set to 0 (in the if statement). You can add an initial value = <> argument in st.metric if you want, but Streamlit will give you a warning that you are setting the value twice. After that, the st.session_state.checked_rows will continue to be updated as the value since that’s the key of the metric. Sorry I didn’t check your key names earlier, have now fixed my comment earlier.

when using this line, I get error messages

MetricMixin.metric() got an unexpected keyword argument ‘key’

or

StreamlitAPIException: Values for st.button, st.download_button, st.file_uploader, st.data_editor, st.chat_input, and st.form cannot be set using st.session_state.

my code:

import streamlit as st
from streamlit import session_state as ss
import pandas as pd

def on_change_function()-> None:
    ss.checked_rows = len(checked_rows)

if 'checked_rows' not in ss:
    ss.checked_rows = 0
if 'average_age' not in ss:
    ss.average_age = 0



#metrics above data_editor
# st.metric(value=ss.checked_rows, label = "checked rows")
# st.metric(key = ss.checked_rows, label = 'checked rows')

# st.metric(value=ss.average_age, label = "average age checked rows")

# data sample
data = {
    'Name': ['John', 'Anna', 'Peter', 'Linda'],
    'Age': [28, 24, 35, 32],
    'Year': [2034, 2023, 2023, 2014]
    }

df = pd.DataFrame(data)
#insert checkbox column
df.insert(0, "check", False)
# de = st.data_editor(df, key = 'data', on_change = on_change_function)
de = st.data_editor(df, key = 'checked_rows', on_change = on_change_function)


checked_rows= de[de['check']]

#checked rows
ss.checked_rows = len(checked_rows)

#average year
if len(checked_rows) != 0:
    avg_age = checked_rows.agg(Age=('Age', 'average')).reset_index()
    ss.average_age = avg_age['Age'][0]

    #metrics below data_editor
# st.metric(value=ss.checked_rows, label = "checked rows")
# st.metric(value=ss.average_age, label = "average age checked rows")``````