I’m building a custom component that embeds a canvas element. I’m testing both the react-based and the vanilla templates.
The component should update the canvas when new data is passed to the component, but without reloading/rerendering from scratch.
I cannot implement this behavior because the component (the iframe content) is rerendered from scratch every time.
My excpetation was that only the render method was called when new arguments are given to it. How to keep the component “mounted” to retain its internal state?
I will try it but I don’t think it will solve my problem. I don’t want to isolate the component from the app’s reruns, what I need is to avoid the unmount/remount of the DOM node.
If the key is the default None, Streamlit autogenerates one. For custom components, this usually leads to them getting a new ID at every interaction and and therefore getting remounted.
Thanks @edsaac for the proposed solution, but it doesn’t work for me.
The arguments of my component contain a “data” parameter, which is a dataframe edited with an st.data_editor. I need to update my component with the value returned by st.data_editor without loosing the state of its internal canvas element.
If I set the key parameter the canvas is retained, as you say, but the component doesn’t receive the updated data.
In fact also the Streamlit ComponentWrapper doesn’t seem to receive updates.
To achieve this behavior, avoid letting React unmount and remount the canvas. Use the useRef hook to persist the canvas element and manipulate it directly without relying on React’s render cycle. Here’s a simplified approach:
Use useRef to hold the canvas element: const canvasRef = useRef(null);
Use useEffect to initialize the canvas only once:
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
// Initialize your canvas here
}, []);
Update the canvas manually when props change:
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
// Update canvas based on new props
}, [props.data]); // Depend on the data that updates the canvas
By managing the updates yourself and not re-rendering the canvas element, the internal state remains intact.
Sure, that’s what I’m already doing. I have a useeffect which depends on the data prop, and I already use a ref for the canvas. It works just fine with a standard React app.
The problem that I’m facing is that, apparently, the whole custom component node is remounted when new data arrives, and it’s clearly visible also with a reactless component.
It can be easily reproduced with the custom component template. If you update the value of the name parameter you will see that the global javascript of the component is executed, not only the render function.
The issue seems to be with how the parent app is handling the component. If the custom component is remounting, ensure the parent doesn’t unmount it by:
Checking if the key prop is changing on the component, as that forces a remount. Use a stable key.
Verifying that the parent app isn’t re-rendering in a way that causes the component to unmount.
If using a framework like Stencil or Web Components, make sure to manage state internally and avoid relying on external re-renders. Feel free to ask me, if your problem still continues or I am missing an important part, in understanding your problem
import streamlit as st
from midicanvas.streamlit_midicanvas import streamlit_midicanvas
import numpy as np
import pandas as pd
import plotly.express as px
st.set_page_config(layout="wide")
if "data" not in st.session_state:
st.session_state["data"] = pd.DataFrame(
np.random.randn(10, 20),
columns=('col %d' % i for i in range(20)))
canvas_container = st.container()
col1, col2 = st.columns(2)
with col1:
st.markdown("#### Editor")
edited_data = st.data_editor(st.session_state["data"], key="editor")
with col2:
st.markdown("#### Plotly scatter")
fig = px.scatter(edited_data)
event_data = st.plotly_chart(fig, on_select="rerun")
with canvas_container:
streamlit_midicanvas("giohappy", edited_data, key="canvas_component")
If I remvoe the key the React app is mounted on every app run. It can be seen in the following video, where the ComponentWrapper.componentDidMount is called on the first run, but also every time I modify the data with the Editor component.
If I set the key niether componentDidMount nor my useEffect is called. It’s like the component is frozen, it doesn’t react to the updated data param.
A similar effect can be reproduced with a reactless component. In that case the remount can be seen when the global js script is called on every run.
Do you have examples of custom components that accept updated data but retain their internal state? I couldn’t find anyone.
I hve inspected the st.data_edtorcomponent but it seems to use specific core setup code,.
The workaround that I’ve seen is to generate a key that is some hash from input data that you want to use to actually trigger a reload of the component. Here is an example of that implementation from the streamlit-folium component:
import pyvista as pv
import streamlit as st
from stpyvista import stpyvista
with st.sidebar:
color = st.color_picker("Color", value="#ff0000")
another_widget = st.slider("Another Widget", 0, 100)
sphere = pv.Sphere()
plotter = pv.Plotter()
plotter.add_mesh(sphere, color=color)
plotter.view_isometric()
cols = st.columns([1, 2])
with cols[0]:
st.title("A variable key")
st.write(f"The selected color is **`{color}`**")
st.write(f"The slider value is **`{another_widget}`**")
with cols[1]:
key = f"stpv_{color}" # <-- my very basic hashing to generate a key
stpyvista(plotter, key)
Thanks again @edsaac but I suspect I’m not explaining well my goal.
Your example with pyvista shows that whether I move the slider or change the color, the html inside the component’s iframe is fully reloaded.
What I’m trying to achieve is (following the pyvista example) being able to change the color of the sphere without reloading the component. If I rotate the sphere, I want its color to change but not the rotation (as an example).
So, using a static key should do the job, but the problem is that in this case the component doesn’t receive the “partial” update (the data parameter).
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.