How to detect changes by javascript in dash

I want to use javascript for speed updates of a lot of elements (id to some value)

Here is the prototype, where I test different ways of changing elements with js. But callback can detect only one of them, fired by Output.

Screen Recording 2024-03-27 at 22.15.07

from dash import html, Output, Input, Dash, no_update
import json
import time

app = Dash(__name__)
app.layout = html.Div(
    [
        html.Button("testing1", "testing1"),
        html.Div(id="test1"),
        html.Div(id="test1_javascript"),
        html.Div(id="test1_javascript_as_component"),
        html.Div(id="test1_javascript_as_reactdom"),
        html.Div(id="log"),
    ]
)

app.clientside_callback(
    """function (n) {
        function render_react_element(element){
            const { type, namespace, props } = element;
            // Validate data (optional, add checks for type and namespace)
            // Construct the element based on type and namespace
            const Element = window[namespace][type];
            console.log(`Element for ${element}:`, typeof(Element), Element);
            const created_element = React.createElement(Element, props);
            return created_element;
        }

        document.getElementById('test1_javascript').innerText=`update with js, n=${n}`;

        let dom_value = window.dash_html_components.H1({"children": `value from reactdom ${n}`});
        console.log('dom_value: ' , typeof(dom_value), dom_value);
        ReactDOM.render(dom_value, document.getElementById('test1_javascript_as_reactdom'));

        if (n) {
                    const element = 
                        """
    + json.dumps(html.H1(f"test1").to_plotly_json())
    + """
                console.log('element: ' , typeof(element), element);
                // document.getElementById('test1_javascript_as_component').innerText=element;

                if (1){
                    const created_element = render_react_element(element);

                    console.log('created_element: ', typeof(created_element), created_element);
                    ReactDOM.render(created_element, document.getElementById('test1_javascript_as_component'));
                }

                return element;
        }
        return window.dash_clientside.no_update
    }""",
    Output("test1", "children"),
    Input("testing1", "n_clicks"),
)


@app.callback(
    Output("log", "children"),
    [
        Input("test1", "children"),
        Input("test1_javascript", "children"),
        Input("test1_javascript_as_reactdom", "children"),
        Input("test1_javascript_as_component", "children"),
    ],
)
def detect_changes_with_javascript(*changes):
    print(f"{changes=}")
    if changes:
        time.sleep(0.5)
        return [html.Div(f"detected {changes=}")]
    return no_update


if __name__ == "__main__":
    app.run_server(debug=True)

Why is it so?
How to change the state of such elements, to make those changes visible in the dash?

Hello @kuplo,

This is due to the nature of JS vs React, when altering only in JS, you dont have anything adjusting the state in React, thus the props never go back to dash.

However, in version 2.16.1+ in dash, you now have access to set_props of dash_clientside which allows for you to trigger updates and callbacks straight from JS.

dash_clientside.set_props(id, propsToUpdate)

This will then trigger updates in the dash ecosystem like you are expecting.

1 Like

You can find an example of what @jinnyzor recommended here:

1 Like

Thanks!
Changed my code, added also changes Store from js. All is detectable now.

For someone who will search for the solution:

from dash import html, Output, Input, Dash, no_update, dcc
import json
import time

app = Dash(__name__)
app.layout = html.Div(
    [
        html.Button("testing1", "testing1"),
        html.Div(id="test1"),
        html.Div(id="test1_javascript"),
        html.Div(id="test1_javascript_as_component"),
        html.Div(id="test1_javascript_as_reactdom"),
        dcc.Store(id="js_data_store", data={}),
        html.Div(id="log"),
    ]
)

app.clientside_callback(
    """function (n) {
        function render_react_element(element){
            const { type, namespace, props } = element;
            // Validate data (optional, add checks for type and namespace)
            // Construct the element based on type and namespace
            const Element = window[namespace][type];
            console.log(`Element for ${element}:`, typeof(Element), Element);
            const created_element = React.createElement(Element, props);
            return created_element;
        }

        // document.getElementById('test1_javascript').innerText=`update with js, n=${n}`;
        dash_clientside.set_props("test1_javascript", {children: `update with js, n=${n}`});

        let dom_value = window.dash_html_components.H1({"children": `value from reactdom ${n}`});
        console.log('dom_value: ' , typeof(dom_value), dom_value);
        // ReactDOM.render(dom_value, document.getElementById('test1_javascript_as_reactdom'));
        dash_clientside.set_props('test1_javascript_as_reactdom', {'children':{'props': {'children': `value from reactdom ${n}`}, 'type': 'H1', 'namespace': 'dash_html_components'}})

        if (n) {
                    console.log('n:', n);
        
                    dash_clientside.set_props("js_data_store", {data: {'value':'new_value'}}); // working
                    
                    
                    const element = 
                        """
    + json.dumps(html.H1(f"test1").to_plotly_json())
    + """
                console.log('element: ' , typeof(element), element);
                // document.getElementById('test1_javascript_as_component').innerText=element;

                if (1){
                    const created_element = render_react_element(element);
                    console.log('created_element: ', typeof(created_element), created_element);
                    // ReactDOM.render(created_element, document.getElementById('test1_javascript_as_component'));
                    var children = JSON.parse(JSON.stringify(created_element))['props'];
                    console.log('test1_javascript_as_component', typeof(children), children);
                    // dash_clientside.set_props("test1_javascript_as_component", {children: element})
                    dash_clientside.set_props("test1_javascript_as_component", {children: [element, element]})
                }

                return element;
        }
        
        return window.dash_clientside.no_update
    }""",
    Output("test1", "children"),
    Input("testing1", "n_clicks"),
)


@app.callback(
    Output("log", "children"),
    [
        Input("test1", "children"),
        Input("test1_javascript", "children"),
        # Input("test1_javascript", "value"),
        Input("test1_javascript_as_reactdom", "children"),
        Input("test1_javascript_as_component", "children"),
        Input("js_data_store", "data"),
    ],
)
def detect_changes_with_javascript(*changes):
    print(f"{changes=}")
    if changes:
        return [html.Div(f"detected at {time.time()} {changes=}")]
    return no_update


if __name__ == "__main__":
    app.run_server(debug=True)

For those who want to understand the speed of set_props: Dash_clientside.set_props 50 times slower than document.getElementById(id).innerText

1 Like