How to update st.columns()

I have this list to populate in columns on streamlit. How do i go about populating it into columns when there is a change to the list?

Calling st.experimental_rerun() is causing an exception.

student_name, student_age, student_course = st.columns([3,3,3])
for index, student  in enumerate(getStudentList(), start=0):

with student_name:
      st.text_input(student.name)
with student_age:
      st.text_input(student.age)
with student_course:
      st.text_input(student_course)

Or is there any way to clear the column and re add them?

If any of the attributes in each column is the same you’ll get a key collision. So I suggest you add a new key per field, e.g. key = f'{student.name}_{index}'. In addition, you can nest each column in a st.container and assign st.empty to the container to clear it.

HTH,
Arvindra

I have tried using st.empty() but it’s not clearing instead it kept adding new row to the column.
Have to do it in a while loop so that it can continuously fetch for new data and update accordingly.


student_name, student_age, student_course, student_button = st.columns([3,3,3, 2])

while True:
      placeholder.empty()
       for index, student  in enumerate(getStudentList(), start=0):

        with student_name:
              placeholder.text_input(student.name)
        with student_age:
              placeholder.text_input(student.age)
        with student_course:
              placeholder.text_input(student_course)
(student_course)
         with student_button:
             isClick= placeholder.button(“enroll”, key= “enroll”)
             If isClick:
                 #do something

        time.sleep(1)

And I have to include an additional button for enrollment. But it keep saying key is not unique for each st.button created. And I realize by running the above code as it is in a while loop many buttons is created even though I only have 1 object in the list. Is placeholder.empty() correctly used in my case?

Hi @qt.qt,

This is how I’d do it using input widgets and columns. It uses a button callback to report the enroll action. Notice how all the widget keys are generated so they are unique. To deal with being able to edit the values from their defaults, and report on them correctly, I am printing out the widgets’ session state values.

A better solution I think, and nicer looking too, would be to use the aggrid component.

Code
import streamlit as st
from dataclasses import dataclass

state = st.session_state

@dataclass
class Student():
    def __init__(self, name, age, course):
        self.name = name
        self.age = age
        self.course = course

def get_students():
    return [
        Student(name='Student A', age=20, course='CS101'),
        Student(name='Student B', age=21, course='CS101'),
        Student(name='Student C', age=23, course='DS101'),
        Student(name='Student D', age=18, course='DS101'),
        Student(name='Student E', age=19, course='CS101'),
    ]

def _enroll_cb(student, index):
    name = state[f'{student.name}_{index}']
    age = state[f'{student.age}_{index}']
    course = state[f'{student.course}_{index}']
    st.info(f'Enrolled: {name}, {age}, {course}')

c1, c2, c3, c4 = st.columns([1,1,1,1])
c1.subheader('Student')
c2.subheader('Age')
c3.subheader('Course')
c4.subheader(' ')

for index, student  in enumerate(get_students(), start=0):
        with c1:
            st.text_input('', student.name, key=f'{student.name}_{index}')
        with c2:
            st.number_input('', min_value=18, max_value=60, value=student.age, key=f'{student.age}_{index}')
        with c3:
            st.text_input('', student.course, key=f'{student.course}_{index}')
        with c4:
            st.write(' ')
            st.button('enroll', on_click=_enroll_cb, args=(student,index), key= f'enroll_{index}')

Happy coding!
Arvindra

Thank you for your reply :slight_smile: currently my design requires the use of a while loop to continuously pull student data from the list and update on the web app. So the text and buttons shld be generated dynamically. Will it still work using your solution? Now facing problems clearing the columns. When new student is added to the list, the columns and rows should get updated accordingly. I have tried using the placeholder.empty() but the column doesn’t get cleared off.

You can rely on the natural updating of the columns content when Streamlit reruns. If get_students() is dynamic, then the columns will automatically adjust themselves. Here’s the extended example, with a dynamic student list and fake continuous update using time.sleep and st.experimental_rerun (you can use a button action to invoke the rerun instead of sleeping).

Code with continuous update
import streamlit as st
from dataclasses import dataclass
import time
import random
import numpy as np

state = st.session_state
if 'message' not in state:
    state.message = 'No student enrolled'

st.info(state.message)

@dataclass
class Student():
    def __init__(self, name, age, course):
        self.name = name
        self.age = age
        self.course = course

    def __repr__(self) -> str:
        return f'|Name: {self.name}, Age: {self.age}, Course: {self.course}|'

def get_students():
    num_students = random.randint(1,10)
    print('', num_students)
    rnd_ages = [random.randint(18,60) for _ in range(num_students)]
    print('', rnd_ages)
    rnd_course_stage = [c for c in np.random.permutation([100, 101, 200, 201, 300, 301, 400, 401, 500, 501])]
    print('', rnd_course_stage)
    rnd_course_subj = [c for c in np.random.permutation(['CS', 'DS', 'CS', 'DS', 'SS', 'CS', 'DS', 'CS', 'DS', 'SS'])]
    print('', rnd_course_subj)
    rnd_courses = [f'{sub}{stage}' for sub, stage in zip(rnd_course_subj, rnd_course_stage)]
    print('', rnd_courses)
    students = [Student(name=f'Student {i}', age=rnd_ages[i], course=rnd_courses[i]) for i in range(num_students)]
    print(students)
    return students

def _enroll_cb(student, index):
    name = state[f'{student.name}_{index}']
    age = state[f'{student.age}_{index}']
    course = state[f'{student.course}_{index}']
    state.message = f'Enrolled: {name}, {age}, {course}'

c1, c2, c3, c4 = st.columns([1,1,1,1])
c1.subheader('Student')
c2.subheader('Age')
c3.subheader('Course')
c4.subheader(' ')

for index, student  in enumerate(get_students(), start=0):
        with c1:
            st.text_input('', student.name, key=f'{student.name}_{index}')
        with c2:
            st.number_input('', min_value=18, max_value=60, value=student.age, key=f'{student.age}_{index}')
        with c3:
            st.text_input('', student.course, key=f'{student.course}_{index}')
        with c4:
            st.write(' ')
            st.button('enroll', on_click=_enroll_cb, args=(student,index), key= f'enroll_{index}')

time.sleep(3)
st.experimental_rerun()

multi-column-widgets-with-continuous-update

That’s all I have time to play around with, so hope you can take it forward from here.

Arvindra

1 Like