1

I'm working at a project in which I have to display graphs. For displaying graphs I'm using vis.js in particular react-vis-network a implementation for using parts of vis.js in React with its stateful approaches.

Initial nodes and edges are loaded before my component is mounted and are passed as props for an initial state.

I attached two eventHandler one direct to a vis.js (the underlying DOM library) and the other at a decorator (button).

The desired/expected behaviour: A node is removed by clicking either the node or the corresponding button.

Observed behavior: Sometimes a node is removed and sometimes a node just disappears for a few ms and is reattached but without a decorator/button.

I already tried to start with an empty state and attaching the nodes,edges in componentDidMount() but I got the same result. I hope you can give me a hint. BTW: Is the way I use to attach components a/the right way? Every other help to improve my class is appreciated also

class MyNetwork extends Component {
    constructor(props){
        super(props);
        let componentNodes = [];
        for (let node of props.nodes){
            componentNodes.push(this.createNode(node));
        }
        let componentEdges = [];
        for (let edge of props.edges){
            componentEdges.push(this.createEdge(edge));
        }
        this.state = {nodes:componentNodes,edges:componentEdges};
        ["_handleButtonClick"].forEach(name => {
            this[name] = this[name].bind(this);
        });
    }

    createNode(node){
        const Decorator = props => {
            return (
                <button
                    onClick={() =>{this._handleButtonClick(props);}}
                >
                    Click Me
                </button>
            );
        };
        node.decorator = Decorator;
        return React.createElement(Node,{...node})
    }
    createEdge(edge){
        return React.createElement(Edge,{...edge})
    }
    addNode(node){
        this.setState({
            nodes: [...this.state.nodes, this.createNode(node)]
        })
    }
    _handleButtonClick(e) {
        if(e){
            console.log("clicked node has id:" +e.id);
            this.removeNode(e.id);
        }
    }
    onSelectNode(params){
        console.log(params);
        window.myApp.removeNode(params[0]);
    }
    removeNode(id) {
        let array = [...this.state.nodes]; // make a separate copy of the array
        let index = array.findIndex(i => i.props.id === id );
        array.splice(index, 1);
        this.setState({nodes: array});
    }
    render() {
        return (
            <div id='network'>
                <Network options={this.props.options} onSelectNode={this.onSelectNode}>
                    {[this.state.nodes]}
                    {[this.state.edges]}
                </Network>
            </div>
        );
    }
}
export default MyNetwork

Before clicking node 2 Before clicking node 2

After clicking node 2 After clicking node 2

Update 1

I created a live example at stackblitz which isn't working yet caused by other failures I make and can't find.

The components I use are:

I reworked my MyNetwork component according to some mistakes xadm mentioned.

  • Components (espacially dynamic) shouldn't be stored in state.

I implemented two new functions nodes() and edges() // line 15-41*

  • key prop should be used, too.

key is used now // line 18 + 32*

  • Passed props cannot be modified, you still have to copy initial data
    into state. State is required for updates/rerendering.

line 9*

*line numbers in live example I mentioned above

Update 2

I reworked my code and now the life sample is working.

My hope is that I could use the native vis.js events and use them in MyNetwork or other Components I will write. I read about using 3rd Party DOM event in this question can't figure out to adapt it for my particular case. Because I don't know how to attach the event handler to . Is this possible to do so I can use the event in other components? Or should I open another question for this topic?

andreask
  • 109
  • 1
  • 13

2 Answers2

1

It sounds like React is re-initializing your component when you are clicking a button. Maybe someone smarter than I am can figure out why that is happening...

But since no one has commented on this yet, one way I have handled these sorts of issues is to take the state management out of the display component. You say you are passing the nodes and edges via props from a parent component. You might consider moving the addNode, removeNode, createEdge, and other methods up to the parent component so that it is maintaining the state of the node/edge structure and your display component <MyNetwork/> is only displaying what it receives as props.

Perhaps this isn't an option in your app, but I generally use Redux to remove the state management from the components all together. I find it reduces situations like this where "who should own the state" isn't always clear.

nbwoodward
  • 2,816
  • 1
  • 16
  • 24
1

I see several possibilities of problems here.

<Decorator/> should be defined outside of <MyNetwork /> class. Click handler should be passed as prop.

Components (espacially dynamic) shouldn't be stored in state. Just render them in render or by rendering method (called from render). Use <Node/> components with decorator prop, key prop should be used, too.

Passed props cannot be modified, you still have to copy initial data into state. State is required for updates/rerendering. You probably need to remove edge(-es) while removing node.

Create a working example (on stackblitz?) if a problem won't be resolved.

xadm
  • 8,219
  • 3
  • 14
  • 25