Pass Values from Python to CustomSlider

Right now the st_custom_slider doesn’t feature any attributes that a Streamlit user would find useful, such as a minimum and maximum range.

How can we enable the user to pass arguments from Python and define some attributes of the slider, like its minimum and maximum range ?

If you look at the docs for the underlying baseui Slider, you will notice that the min and max props can be passed to limit the range of the slider.

So let’s start by changing the content of the CustomSlider.tsx to add fixed values to the given props:

import React, { useEffect, useState } from "react";
import { Streamlit, withStreamlitConnection } from "streamlit-component-lib";
import { Slider } from "baseui/slider";

const CustomSlider = () => {
  const [value, setValue] = useState([10]);

  useEffect(() => Streamlit.setFrameHeight());

  return (
    <Slider
      value={value}
      onChange={({ value }) => value && setValue(value)}
      onFinalChange={({ value }) => console.log(value)}
      min={10}
      max={1000}
    />
  );
};

export default withStreamlitConnection(CustomSlider);

This should properly set up the range of your slider. But we would prefer those values to be transferred by the user from the Streamlit script.

Let’s return to the __init__.py file, where we defined the connection between Python and the frontend. The _component_func—returned by declare_component—is responsible for “accessing” the component. Any argument used to call the function is passed as props to the React component through a Javascript event.

Only JSON-seriazable data and Dataframes can be transferred from Python to the frontend component, so make sure that you pass Python native types or Pandas Dataframes.

Try it by passing a min and max variable in _component_func and retrieve it from the React code, then edit your __init__.py file accordingly:

import streamlit.components.v1 as components

_component_func = components.declare_component(
    "custom_slider",
    url="http://localhost:3001",
)

def st_custom_slider():
    # Pass min and max from Python to the frontend component
    component_value = _component_func(min=0, max=42)
    return component_value

Print props of the CustomSlider function in CustomSlider.tsx and declare those props as your function’s arguments :

import React, { useEffect, useState } from "react";
import {
  Streamlit,
  withStreamlitConnection,
  ComponentProps,
} from "streamlit-component-lib";
import { Slider } from "baseui/slider";

const CustomSlider = (props: ComponentProps) => {
  console.log(props.args);
  const [value, setValue] = useState([10]);

  useEffect(() => Streamlit.setFrameHeight());

  return (
    <Slider
      value={value}
      onChange={({ value }) => value && setValue(value)}
      onFinalChange={({ value }) => console.log(value)}
      min={10}
      max={1000}
    />
  );
};

export default withStreamlitConnection(CustomSlider);

Reload your browser, open your JS console and have a look at your print logs.

Log props

So you get props.args = { min: 0, max: 42 } . The props dictionary has an args key in which all arguments from _component_func are being passed as a JS object.

You should see the props printed everytime you interact or render the component, which can result in a lot of printing. In more sophisticated components, you may need to throttle or debounce such calls to prevent unecessary rapid-fire function calls (which may break your browser).

Arguments passed from Python to React props is a specific behavior of the React template. The arguments are actually passed through a Javascript event forwarded by the Python call, and the withStreamlitConnection class handles transferring those as props of the CustomSlider.

Conclude the exercise by passing a label, min and max value from the app.py file all the way down to the React component props. Since we are using Streamlit, we can use Streamlit widgets to interactively define the label, min, and max arguments.

app.py

import streamlit as st
from streamlit_custom_slider import st_custom_slider

st.title("Testing Streamlit custom components")

# Add Streamlit widgets to define the parameters for the CustomSlider
label = st.sidebar.text_input('Label', 'Hello world')
min_value, max_value = st.sidebar.slider("Range slider", 0, 100, (0, 50))

# Pass the parameters inside the wrapper function
v = st_custom_slider(label=label, min_value=min_value, max_value=max_value)
st.write(v)

__init__.py

import streamlit.components.v1 as components

_component_func = components.declare_component(
    "custom_slider",
    url="http://localhost:3001",
)

# Add label, min and max as input arguments of the wrapped function
# Pass them to _component_func which will deliver them to the frontend part
def st_custom_slider(label: str, min_value: int, max_value: int):
    component_value = _component_func(label=label, minValue=min_value, maxValue=max_value)
    return component_value

CustomSlider.tsx

import React, { useEffect, useState } from "react";
import {
  ComponentProps,
  Streamlit,
  withStreamlitConnection,
} from "streamlit-component-lib";
import { Slider } from "baseui/slider";

const CustomSlider = (props: ComponentProps) => {
  /**
   * Destructuring JSON objects is a good habit.
   * This will look for label, minValue and maxValue keys
   * to store them in separate variables.
   */
  const { label, minValue, maxValue } = props.args;

  const [value, setValue] = useState([10]);

  useEffect(() => Streamlit.setFrameHeight());

  // Add a label and pass min/max variables to the baseui Slider
  return (
    <>
      <h3>{label}</h3>
      <Slider
        value={value}
        onChange={({ value }) => value && setValue(value)}
        onFinalChange={({ value }) => console.log(value)}
        min={minValue}
        max={maxValue}
      />
    </>
  );
};

export default withStreamlitConnection(CustomSlider);

There it is! Python arguments are propagated all the way to the underlying baseui slider and its properties are modified accordingly.

Custom Slider


Did you notice a problem on the demo ?

Everytime an input argument of st_custom_slider on the user side changes, the CustomSlider gets new props. Consequently, the component is considered a new object and is remounted…which means your older slider is dropped and a new slider is created. This behavior causes the component to “flicker” if you edit the parameters regularly and the state of your older slider isn’t preserved when the CustomSlider is recreated!

We need a way to prevent the component from recreating itself every time its properties change.