Creating multiple custom components in the same python package

Hello Streamlit Community!

Is it possible to develop multiple streamlit custom components in same python package and expose them ? Something like material-ui react lib, where multiple UI components can be exported from.

Hello @Jacob_Lukose, welcome to the community :slight_smile:

Haven’t given it too much thought but 2 ideas coming to mind (sorry the following is going to be very rough pseudocode, I’ll try again later if that doesn’t help you):

1/ on Python side declare one URL per component then have react-router or Next.js on the React side check the route and send back the desired component. My guess is this is not the optimal solution but it may be a way to start and better understand the path to take.

Roughly something like:

_component_func_button = components.declare_component("http://localhost:3001/button")
_component_func_slider = components.declare_component("http://localhost:3001/slider")

def material_button(label: str):
    _component_func_button(label=label)

def material_slider(min: int):
    _component_func_slider(min_value=min)

and React side

<Switch>
  <Route path="/button">
	<Button label="args.label"/>
  </Route>
  <Route path="/slider">
	<Slider min="args.min_value"/>
  </Route>
</Switch>

2/ from Python to send the component to display as a prop, then in your React code check the prop and return the desired component. This may be better than 1/.

Roughly something like:

_component_func = components.declare_component("...")

def material_button():
    _component_func(component="button")

def material_slider():
    _component_func(component="slider")

then on the React side

<App>
    if(args.component == "button") {return Button(args.label)}
    if(args.component == "slider") {return Slider(args.min_value)}
</App>

@synode / @asehmi maybe you have more experience on this to share though :slight_smile: ?

2 Likes

Hello @Jacob_Lukose,

@andfanilo’s insights on how to make a collection of multiple custom components are great, but they require you to write boilerplate code for every element you want to bundle in your component package. If you have only a few elements to maintain, it is great, but if you plan to port a whole framework library like Material UI, it’ll require quite some work.

To deal with many elements, you can try to make generic code where:

  • Python side you’ll pass some React component name as string, along with its props
  • React side, render it dynamically.

However, there is one major issue you will face if you try to port Material UI to streamlit with this method: custom components are displayed in iframes, which are slow to load, and each element will be rendered in its own iframe. If you attempt to display too many of them (I’d say like around 10 and above), it will take a few seconds for everything to load. And currently, if your components takes too much time to load, Streamlit will show a warning message in place of your element.

Now to overcome this issue, it’s going to be a little bit more complex. A solution would be to show mulitple elements at once, in a single iframe. But to do so, you’ll need to describe in some way all the UI components you want to display, send them at once to React, and have some javascript code which parses your code to render all the components the way you want.

Now spoiler alert: If your initial idea was to port Material UI to Streamlit, I’m currently working on the matter, along with some other cool React components :stuck_out_tongue: @Charly_Wargnier has tweeted about it awhile ago.

4 Likes

:rocket:

1 Like

Took a little time to think about this and my current feeling is that the main use case for having widgets rendered within a component is to host multi-input forms. Most other visuals and simple one-at-a-time inputs can be handled directly in Streamlit.

There’s a W3C standard forms description language called XForms, and some pretty good tooling already available. For example, see Enketo.

Here’s a demo which applies an XForms transformer to generate HTML/JavaScript to display in a web browser. Perhaps this generated view can be displayed in a component? When the form is submitted, its values can be sent to Streamlit for further processing. I imagine the transformer can be modified to generate a full component implementation with the HTML form embedded.

Since XForms is an abstract textual representation of a form, it can also serve as the spec for the Streamlit side (if needed).

The Python library pyxform, takes very sophisticated form specs designed in Excel and converts them to XForms. Then the Enketo transformer can be used to generate the HTML/JS form implementation. Technically one should be able to generate

Read about XLSForm.

I haven’t studied Orbeon Forms, which is XForms compatible.

Thankyou @andfanilo @synode @asehmi . Will spend some time and try out all these.

Arent the UI components streamlit providing out of the box like st.write(…), st.radio(…) etc also rendered under iframes ? Or is this something only for custom components ?