At this stage, we have built an unidirectional data flow where Python arguments are being sent to the React component through JavaScript events and collected into the component’s props. We would now like to retrieve the internal state of the widget back into our Streamlit script using Streamlit.setComponentValue
.
The Streamlit.setComponentValue
method stores the component’s value internally, then triggers a Streamlit rerun. The Streamlit rerun will retrieve the new component’s value and store it back into the Python variable assigned to your st_custom_slider
call.
Head back to CustomSlider.tsx
. We want to rerun the Streamlit script on mouse release — we can do this by integrating the Streamlit component value setting as a callback of the onFinalChange
event in CustomSlider.tsx
:
import React, { useEffect, useState } from "react";
import {
ComponentProps,
Streamlit,
withStreamlitConnection,
} from "./streamlit";
import { Slider } from "baseui/slider";
const CustomSlider = (props: ComponentProps) => {
const { label, minValue, maxValue } = props.args;
const [value, setValue] = useState([10]);
useEffect(() => Streamlit.setFrameHeight());
// Send data back to Streamlit in onFinalChange event
return (
<>
<h3>{label}</h3>
<Slider
value={value}
onChange={({ value }) => value && setValue(value)}
onFinalChange={({ value }) => Streamlit.setComponentValue(value)}
min={minValue}
max={maxValue}
/>
</>
);
};
export default withStreamlitConnection(CustomSlider);
Just to make sure we’re all on the same page, your __init__.py
should look like the following:
import os
import streamlit.components.v1 as components
_component_func = components.declare_component(
"custom_slider",
url="http://localhost:3001",
)
def st_custom_slider(label: str, min_value: int, max_value: int, key=None):
component_value = _component_func(label=label, minValue=min_value, maxValue=max_value, key=key)
return component_value
Let’s put a Streamlit native slider and our custom slider side-by-side in our app.
app.py
import streamlit as st
from streamlit_custom_slider import st_custom_slider
# Streamlit native slider
v = st.slider("Hello world", 0, 100)
st.write(v)
# Streamlit custom slider
v_custom = st_custom_slider('Hello world', 0, 100, key="slider1")
st.write(v_custom)
What can we see from this gif? The native slider makes the script rerun as long as the slider moves. The custom slider, with Streamlit, reruns only on mouse release and we retrieve the value of the slider on the Python side. So far, so good!
However, there are some issues with our implementation:
CustomSlider
, which returns None
before the first render of the component.v_custom
we get back from st_custom_slider
is an array, as our CustomSlider
stores its state as an array of number for the baseui
Slider (the reason being so that it can also take an array of two numbers to render a RangeSlider). We would prefer st_custom_slider
to return an int
instead of an array, though we don’t want to do too much type manipulation on the React side.Fortunately, these two issues can be addressed at the Python wrapper level in __init__.py
.