Hello everyone,
I have been interested in Streamlit since the beginning of the year. Initially, I was very enthusiastic about the product and its simplicity, but now I feel that as soon as we go beyond basic use, especially when defining our own components, the product’s stability is lacking.
Unfortunately, I can’t prove it, but what used to work no longer does, even when reverting to earlier versions of Streamlit. Of course, some might say that I must be imagining things, that the code I developed doesn’t work and never did.
I’ll provide a recent example where everyone can judge and critique the code I wrote and perhaps show me that I am wrong.
I used “Replit” to create a small Streamlit application that defines an external component, which is just a button using React and Ant Design.
You can access my code using the following link: Sign Up - Replit
The page of interest displays three buttons and the value returned when clicking on any of them.
When switching from one button to another, everything is fine: the value “true” is returned for the clicked button, and “false” is returned for the other buttons.
The two buttons on the left are “custom” buttons (external components). The button on the right is a standard Streamlit button (st.button).
Now, let’s click multiple times in a row on the same button. The standard Streamlit button always returns “true”, while the “custom” buttons alternate between “true” and “false,” which is absurd.
Why? I have no idea. Some “clicks” result in a re-instantiation of the component without activating the JavaScript code…
If you know the answer to what, for me, is a mystery, I would be grateful if you could explain it.
Kind regards.
Some additional explanations:
In theory, implementing a component that returns a true or false value should be an easy task, but in reality, it is quite complicated.
JavaScript code cannot simply return true or false. Once the button has been used, it must be reinstantiated to function correctly in the next iteration and indicate that its default value is false. The only way to ensure it will be reinstantiated is to implement a counter and increment the counter each time the button is pressed.
So if we are looking at the actual implementation we have:
In the implementation of the react component:
class Button(ReactComponent):
def click():
Streamlit.setComponentValue(self.props.counter + 1)
def render():
props = dict()
props[
'onClick'] = self.props.onClick if self.props.onclick else self.click
props['type'] = self.props.type
props['disabled'] = self.props.disabled
props['danger'] = self.props.danger
props['size'] = self.props.size
props['ghost'] = self.props.ghost
props['block'] = self.props.block
props['shape'] = self.props.shape
props['loading'] = self.props.loading
props['iconPosition'] = self.props.iconPosition
if self.props.icon:
props['icon'] = React.createElement(icons[self.props.icon], {})
return React.createElement(antd.Button, props, self.props.label)
self.props is an object containing the parameters of the component. The “counter” property is incremented by one when the button is pressed and the value is returned to the python caller.
The python code encapsulating the component is the following:
def use_Button(component):
class Component:
def __init__(self, id=None, **d):
id = f'Component_{id}'
if id not in st.session_state:
st.session_state[id] = dict(old=0, new=0)
result = component(id=id,
counter=st.session_state[id]['new'],
default=st.session_state[id]['new'],
**d)
if result != st.session_state[id]['new']:
st.session_state[id]['new'] = result
if st.session_state[id]['old'] != st.session_state[id]['new']:
self.result = True
st.session_state[id]['old'] = st.session_state[id]['new']
else:
self.result = False
def get(self):
return self.result
return Component
The first interesting instruction is:
result = component(id=id, counter=st.session_state[id]['new'],
default=st.session_state[id]['new'], **d)
Here we are defining a component with 3 important parameters: an id, a counter and a default value which is the last value of the counter or 0 is the counter has not been used.
2 cases can occur:
a) the result is equal to the default value ==> that means that the javascript code has NOT been called meaning that the button has not been pushed.
b) the result is different of the last recorder value of the button ==> that means that the javascript code has been activated due to a push on the button
Everything is OK if we switch from one button to another.
But if we push the same button several times there are clicks that are not followed of javascript code activation.
The default value of a component should be taken into account only at the first iteration when the component has not been displayed yet.
During subsequent iterations, the JavaScript code should be activated systematically.
This is not the case in this current example