Handling st.data_editor changes exactly once?

Summary

I am using st.data_editor and is it ALMOST working correctly.
I have a pseudo-ORM list of objects connected to streamlit. I want to set it up that whenever a cell is edited, the value is immediately updated in the database.
I use the edited_rows state variable to know what was changed, and pass it back to the ORM objects using setattr(obj, changed_key, new_value).
However, I notice that after I make a second edit, both edits appear in the edited_rows dictionary. This causes my setup to handle the same change multiple times.

I tried clearing it using st.session_state[key]["edited_rows"].clear() but that did not have any effect.
Is this intentional behavior? Is there a solution for this use case?

Debug info

  • Streamlit version: 1.23.1
  • Python version: 3.11
  • Using Conda?
  • OS version: Ubuntu
  • Browser version: Firefox
2 Likes

The behavior you’re experiencing with the edited_rows dictionary in Streamlit’s st.data_editor is intentional. The edited_rows dictionary accumulates all the edits made in the data editor until the app is rerun or the session state is reset. This behavior allows you to access and handle all the changes made by the user during the session.

To address your use case of handling each change only once, you can modify your code to keep track of the changes you have already processed. Here’s a possible approach:

  1. Create a set to store the keys of the already processed changes:
if "processed_changes" not in st.session_state:
    st.session_state.processed_changes = set()
  1. Iterate over the edited_rows dictionary, but only process the changes that haven’t been processed before:
for row_key, changed_values in st.session_state.edited_rows.items():
    if row_key not in st.session_state.processed_changes:
        # Process the changes for this row
        obj = your_orm_objects[row_key]
        for column_key, new_value in changed_values.items():
            setattr(obj, column_key, new_value)

        # Add the row key to the set of processed changes
        st.session_state.processed_changes.add(row_key)

By using the set processed_changes to keep track of the changes that have already been processed, you ensure that each change is handled only once, even if multiple edits occur.

Remember to adapt the code above to fit your specific use case, including the names of your ORM objects and the appropriate logic for processing the changes.

I hope this helps resolve the issue and allows you to handle each change correctly in your Streamlit app.

1 Like

Thanks! I indeed went on with a similar approach:

    def _handle_table_changed(self, key_name: str):
        new_state = st.session_state[key_name]
        if "edited_rows" in new_state:
            for index, change_dict in new_state["edited_rows"].items():
                source_object = self.data[index]
                for changed_field, new_value in change_dict.items():
                    # the getattr() check is required because streamlit does not remove entries from the modification
                    # dictionary. 
                    if getattr(source_object, changed_field) != new_value:
                        setattr(source_object, changed_field, new_value)
            # new_state["edited_rows"].clear() Disabled because no effect

    def render(self, key_name: str):
        st.data_editor(
            self.dataframe,
            column_config=self.st_column_specs,
            key=key_name,
            column_order=self.column_names,
            hide_index=True,
            on_change=self._handle_table_changed,
            args=[key_name],
        )

Because a row can be modified multiple times, I have to check on a per-value basis (this is the getattr line). It can theoretically cause performance issues in large sessions, but I’ll cross that bridge when we get there…

1 Like

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