Broken Rendering with Redux + Model Serialization/De-Serialization

Author: jamal-ahmadCreated Sep 19, 2020Updated Apr 2, 2025
Labelsquestionanswered

So i'm trying to setup a simple app with redux as the global store. The graph is stored in a slice as a simple serialized model object. On every change to the DiagramModel entities (nodes, links), the DiagramModel is serialized and written back into the store.

typescript
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import createEngine, { DiagramModel } from "@projectstorm/react-diagrams";
import { CanvasWidget } from "@projectstorm/react-canvas-core";
import { setGraph, fetchGraph } from "../features/graph/graphSlice";
import { RootState } from "./rootReducer";
import { SimpleSerializedGraph } from "../features/graph/types/simple";
import "./App.css";

const App: React.FC = () => {
    console.log("I got called");
    const [engine, setEngine] = useState(createEngine());

    const dispatch = useDispatch();
    const graph: SimpleSerializedGraph = useSelector(
        (state: RootState) => state.graph
    );

    // emulate graph data being loaded from the server
    useEffect(() => {
        console.log("loading graph");
        dispatch(fetchGraph());
    }, []);

    let model = new DiagramModel();
    let obj: ReturnType<DiagramModel["serialize"]> = JSON.parse(
        JSON.stringify(graph)
    );
    model.deserializeModel(obj, engine);
    console.log(model.serialize());
    model.getModels().forEach((item) =>
        item.registerListener({
            eventDidFire: () => {
                console.log("WOOSH");
                dispatch(setGraph(engine.getModel().serialize()));
            },
        })
    );
    engine.setModel(model);

    return (
        <React.Fragment>
            <CanvasWidget className="diagram-container" engine={engine} />
        </React.Fragment>
    );
};

export default App;

However this is resulting in very broken rendering. (see pic below). I've got a couple of questions:

  • Is there something that i'm doing wrong here?
  • if a state management solution is used, how is that global state supposed to be updated?
  • Should I store something else in the store e.g. the ModelEngine object itself? not sure how feasible that would be with redux
  • is redux not a good choice for react-diagrams? maybe something like mobx or react context? I hope this isn't the case because redux tooling is very nice and it would be shame if the react-diagrams and redux were at odds.
Screen Shot 2020-09-18 at 8 19 07 PM

the dummy data being used is as follows:

typescript
export const initialData: SimpleSerializedGraph = {
    "id": "initialData",
    "offsetX": 0,
    "offsetY": 0,
    "zoom": 100,
    "gridSize": 0,
    "layers": [],
}

export const dummyData: SimpleSerializedGraph = {
    "id": "dummyData",
    "offsetX": 0,
    "offsetY": 0,
    "zoom": 100,
    "gridSize": 0,
    "layers": [
        {
            "id": "28",
            "type": "diagram-links",
            "isSvg": true,
            "transformed": true,
            "models": {
                "36": {
                    "id": "36",
                    "type": "default",
                    "source": "32",
                    "sourcePort": "33",
                    "target": "34",
                    "targetPort": "35",
                    "points": [
                        {
                            "id": "37",
                            "type": "point",
                            "x": 147.234375,
                            "y": 133.5
                        },
                        {
                            "id": "38",
                            "type": "point",
                            "x": 409.5,
                            "y": 133.5
                        }
                    ],
                    "labels": [],
                    "width": 3,
                    "color": "gray",
                    "curvyness": 50,
                    "selectedColor": "rgb(0,192,255)",
                }
            }
        },
        {
            "id": "30",
            "type": "diagram-nodes",
            "isSvg": false,
            "transformed": true,
            "models": {
                "32": {
                    "id": "32",
                    "type": "default",
                    "x": 100,
                    "y": 100,
                    "ports": [
                        {
                            "id": "33",
                            "type": "default",
                            "x": 139.734375,
                            "y": 126,
                            "name": "Out",
                            "alignment": "right",
                            "parentNode": "32",
                            "links": [
                                "36"
                            ],
                            "in": false,
                            "label": "Out"
                        }
                    ],
                    "name": "Node 1",
                    "color": "rgb(0,192,255)",
                    "portsInOrder": [],
                    "portsOutOrder": [
                        "33"
                    ]
                },
                "34": {
                    "id": "34",
                    "type": "default",
                    "x": 400,
                    "y": 100,
                    "ports": [
                        {
                            "id": "35",
                            "type": "default",
                            "x": 402,
                            "y": 126,
                            "name": "In",
                            "alignment": "left",
                            "parentNode": "34",
                            "links": [
                                "36"
                            ],
                            "in": true,
                            "label": "In"
                        }
                    ],
                    "name": "Node 2",
                    "color": "rgb(192,255,0)",
                    "portsInOrder": [
                        "35"
                    ],
                    "portsOutOrder": []
                }
            }
        }
    ]
}

and the types being used are (i deduced them from the lib source code):

typescript
export interface SimpleSerializedBaseModel {
    type: string;
    selected?: boolean;
    extras?: any;
    id: string;
    locked?: boolean;
};

// this will allow abitrary proeprties to be added to the model
// as along as the base properties are present
export interface SimpleSerializedModel extends SimpleSerializedBaseModel {
    [prop: string]: any;
};

export interface SimpleSerializedLayer {
    isSvg: boolean;
    transformed: boolean;
    models: { [x: string]: SimpleSerializedModel };
    type: string;
    selected?: boolean;
    extras?: any;
    id: string;
    locked?: boolean;
}

export interface SimpleSerializedGraph {
    offsetX: number;
    offsetY: number;
    zoom: number;
    gridSize: number;
    layers: SimpleSerializedLayer[];
    id: string;
    locked?: boolean;
};

Source: projectstorm/react-diagrams