React component : Multi-users synchronisation

Hi Everyone !

The big picture of what I’m trying to achieve :
I’m developing a Dash application to control some output of a Raspberry-pi. The issue with Dash is that it is a stateless app therefore each user only see and interact with it’s own state and not the state of the Raspberry pi output. Let’s take an example :

My dashboard show a slider that will control a fan speed on the raspberry pi. If multiple users are connected to the Rpi, when one will change the slider, this change won’t be reflected to other users session. So there will be synchronisation issue between each user session, and the actual value that the Rpi is outputting for the fan speed.

I could use some workaround using websocket and polling the actual state of the Rpi, but my project is much more complicated than this example and I need a more general solution.

The solution I’am aiming for :
Having a custom component called a Synchronizer. It take as input a list of children like any bootstrap component for example, and a list of props to synchronize for all those children. Under the hood, the Synchronizer component will listen to a state change for the desired props among his children, when a change happen it will send a message using WebSocket to a Quart server running on a specific url (that could also be an input of the Synchronizer). The Quart server will reply to the same url and send the id and props value that have been updated to all users, updates that will be applied via the Synchronizer.
Tell me if i’m not clear here, I very new to react and I’ve tendency to confuse props and state (the value of a slider in dash, is it a prop or a state in the react slider component ?)

Solution that already exist :
A fork of dash was developed called dash_devices, it’s looking very good but being a fork of the main dash project, I’m not sure how compatible is it with recent version of dash, how it will be maintained ect… also it is changing a lot of the core aspect of dash…
There is also the WebSocket component on the dash-extension, which I’m using as source of inspiration for my component.
Aware of an other already existing solution ? let me know.

The issues I’m facing :
I’ve developed a lot in python, C#, C++… but when I started to look at React… I got very confused, even though I did a bit of javascript back in the time. So far I managed to build and run the dummy boiler-plate provided by dash, I did tinker it a bit but I’m having trouble to simply render the children component that have been passed to my Synchronizer component…

I’ve done this post to have your opinions regarding the solution I’m aiming for, does it sound reasonable for a React beginner ? Does the idea even make sense ?
If I manage to go through this, the code will be available on github as I think I’m not the only one that need such functionality.

In the meantime :
I’m stuck on this code that should be simple :

    constructor(props) {
        super(props);
        this.ChildRef = React.createRef();
    }

    componentDidUpdate(prevProps) { /*Fetch the state that was changed in the children and send a WebSocket msg.
       TODO LATER*/
    }

    render() {
        const children = this.props.children;
        return (
            //This is Obviously not working...
            children.forEach(Child => {
                return <Child ref={this.ChildRef}/>
            });
        );
    }
//The PropTypes I'm using for the children prop
children: PropTypes.node,

For each children passed via dash to the component, I want to render the children and use the React.CreateRef function has described here to be able to fetch the component state in the componentDidUpdate function.

Big post, thank you for your time If you went all the way :slight_smile:

Update on the meantime :
So it seems that getting the child state from the parent wasn’t that easy. Children being either functional component or class component I can’t use ref or hook to access their state. The solution I found come from here, a function that find the react instance attached to a DOM element. With that I’m able to grab children state when the parent component update :

Code
export default class Synchronizer extends Component {

    constructor(props) {
        super(props);

        //Problably a better way to do that using Object destructuring or reduce
        const synced_props = parseToArray(props.synced_props);
        this.synced_state = {};
        props.children.forEach(child => {
            this.synced_state[child.key] = {};
            synced_props.forEach(prop => {
                this.synced_state[child.key][prop] = child.props._dashprivate_layout.props[prop];
            }, this);

        }, this);
    }

    /*
    *   Look throught the children for the synced props and update those that has been modified
    */
    updateSyncedProps(){
        const synced_props = parseToArray(this.props.synced_props);
        this.props.children.forEach(child => {
            var reactInstance = FindReact(document.getElementById(child.key));

            synced_props.forEach(prop => {
                if(reactInstance.props.hasOwnProperty(prop)){
                    //We check the value has indeed changed
                    if (this.synced_state[child.key][prop] != reactInstance.props[prop]){
                        this.synced_state[child.key][prop] = reactInstance.props[prop];
                        console.log(`Child id ${child.key} has props ${prop} whose value changed to ${reactInstance.props[prop]}`);
                    }
                }
            }, this);
        }, this)
    }

    /*
    This is called when this component is updated. We are updating the synced state at this moment
    */
    componentDidUpdate(prevProps) {
        this.updateSyncedProps();
    }

    render() {
        return (
            <div>
                {this.props.children}
            </div>
        );
    }
}

Synchronizer.defaultProps = {};

Synchronizer.propTypes = {
    /**
     * The ID used to identify this component in Dash callbacks.
     */
    id: PropTypes.string,

    /**
     * The list of children component that on who we want to sync props
    */
    children: PropTypes.node,

    /**
     * String or List of string of the props to synchronize.
    */
    synced_props: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.arrayOf(PropTypes.string)
    ]),

    /**
     * Dash-assigned callback that should be called to report property changes
     * to Dash, to make them available for callbacks.
     */
    setProps: PropTypes.func
};

I tested it with a few dash component from dash-daq (slider, knob, toggle) with success.
If there is a better way, let me know.

Hello @Eledwin

Not sure if this would help you, but have you thought about storing the changes in a database? If you do that, then you could use an n_intervals prop to check for updates from the database, comparing your current state vs the database state. If there is a difference, then you would update the element. If not, then Raise PreventUpdate.

This would be done as a callback.

Are you planning on submitting the changes realtime as you change sliders, or would you submit the changes on a button press. Depending on how many users you plan on having using the site, I’d recommend the later if it is going to be a more than a handful.

Hi @jinnyzor

I kinda thought about it. It would probably be a good solution to address my issue, the main drawback would be :

  • Not a realtime setup, I have to pull the value from the database every x seconds (A lot of useless request when nothing is changing on the UI)
  • A lot of redundant callback need to be define to link components props to the database (pattern matching callback could help, but also introduce complexity and limitation as an Output can only be used in one callback)

To be honest I didn’t looked that much into this idea, it may be easier to implement, I’ll think about it a bit more. But I’m making pretty good progress with my Synchronizer (So far I’ve a working draft that sync slider and knob accros 2 browsers tab).

Yes the change are done in Realtime, the app I’m doing is a home automation to control our homemade house smart heating system (A thermal solar collector based heating system). It will be used to monitor and control pump, valve, temperature ect… So realtime is better. But has you pointed out, I won’t have 50 users on the site, it’s just in case several family members are accessing / viewing the site at the same time.

@Eledwin

Instead of creating outputs for each component, you could recreate the whole page-content on the callback. Depending on the information in your data query you could update one, add one, remove one or update all of the components.

I actually am using the above mentioned code to be able to apply filters across multiple charts.

Once you figure out how you would like to send the message to the client to trigger the pull, then that might be able to help you.

UPDATE

I kinda managed to have a draft that was working but not with all component and only when the Dash app was in debug mode (I was directly trying to access the component trough DOM and it has a lot of issue). So I rethink it differently and it’s working pretty nice but it is “breaking” the callback in python side.

So, I’ll try to make it simple. (Pretty hard right now to make a Minimal Working Example).

This is my dash layout with one callback :

Code
app.layout = html.Div([
	synchronizer.Client(id="SynchronizerClient", url=f"ws://{synchronizer.Server.get_ip()}:5000/ws_synchronizer", debug = True),
   	synchronizer.Synchronizer([
	   	daq.Knob(
			id = "knob_value",
			label = {"label": "Value = 40"},
			size = 180,
			min = 0,
			max = 100,
			value = 40,
			scale = {'start': 0, 'labelInterval': 2, 'interval': 10}
		)
    ], synced_props = ["value"], debug = True),
])
@app.callback(
	Output("knob_value", "label"),
	Input("knob_value","value")
)
def onChange1(value):
	return f'Value = {value}'

I won’t go in the detail on how my Synchronizer react component work, but basically I’m using web socket to received real-time information to update a specific prop for a specific component ID. I’m applying this received prop value to the Knob component in the render method of the Synchronizer component has shown below :

Code
    render() {
        const children = parseToArray(this.props.children).map((child,idx) => {
            var dashprivate_layout_props = child.props._dashprivate_layout.props;
            for(const [prop, value] of Object.entries(this.state[child.key])){
                dashprivate_layout_props[prop] = value.value;
            }

            const child_props = {...child.props,
                _dashprivate_layout: {...child.props._dashprivate_layout,
                    props: dashprivate_layout_props
                }
            };
            return React.cloneElement(child, child_props);
        }, this);

        return (
            <div id={this.id}>
                {children}
            </div>
        );
    }

As you can see, I’m using the _dashprivate_layout to modify the prop value of the Knob component. This is working really great so far. The issue I’m having is when I change the prop of a child like this, it doesn’t trigger the callback on the python side. So on my html page, the value of the Knob did change, but not the label that is updated via the python callback…

Does someone know how Dash work internally and what is the “way” to modify a children prop without breaking Dash functionality ? Thanks.

@Eledwin,

Sounds like you are manipulating an element clientside and wanting that to be scene from the server?

Manipulating through JavaScript is tricky, and things get out of sync, as you can see.

  1. Can you add a hidden Button that you can trigger a click at the end of your manipulation via React.js?
  2. Have you looked at clientside_callbacks?

@jinnyzor
As I was saying in the previous post, I’m trying to build a component in react in order to avoid using the callback functionality of Dash. This because I would have to create a callback output for each prop I want to sync, and it’s a lot of redundant code and create issue when I want to use the output somewhere else.

You proposed the solution to re-create the whole-page content but it would be very inefficient in term of reactivity and data transferred between the server and the client. I know you are offering solution using the intended workflow for Dash app and I would go that way if what I’m trying to achieve was impossible without modifying the core functionality of Dash. But I’m really close to make it work, just need to find the right way for a React component to modify a Dash compiled component.