react-cytoscapejs
react-cytoscapejs copied to clipboard
examples using hooks / useState ?
I'm trying to create a functional component with this lib, and useState hooks, but having problems dealing with the cy instance.
I need to add events to the cy instance, eg tap. but this seems to create a memory leak - now each time the graph is rendered again, there's another 'graph' created. tapping will now send 3, 4, ... etc events.
what's the best way to manage this?
do i need to put some code in to try and remove the cy.on(...) event before the next render?
Thanks!
code is like this below
import React, { useState, useEffect } from 'react';
import Cytoscape from 'cytoscape';
import CytoscapeComponent from 'react-cytoscapejs'
import cola from 'cytoscape-cola';
// import cydagre from 'cytoscape-dagre';
import { graphStyle, nodeStyle } from './graphStyle'
const layout = {
name: 'cola'
}
Cytoscape.use(cola);
// import { DcLib } from '../utils/DcLib'
const KGraph = (props: any) => {
const graph = props?.graph
const [cy, setCy] = useState({})
useEffect(() => {
console.log('effect')
// cy.layout(layout).run()
})
console.log('graph', graph)
const initCy = (cy: any) => {
// @ts-ignore
console.log('initCy')
if (!cy) {
setCy(cy)
}
cy.on('tap', (event: any) => {
console.log('tap cy', event)
console.log('target', event.target)
});
}
if (!props.graph.ready) {
return (<div> graph here </div>)
}
const layoutGraph = () => {
// @ts-ignore
cy.layout(layout).run()
}
return (
<div>
<button onClick={() => layoutGraph()}>redo graph</button>
<CytoscapeComponent
cy={initCy}
elements={graph.elements}
style={graphStyle}
layout={layout}
stylesheet={nodeStyle}
/>
</div>
)
}
export default KGraph
I don't think I had this problem in my application using useRef with a couple of checks and cleanup.
function MyComponent() {
const cyRef = useRef<CytoscapeRef | null>();
// cleanup cytoscape listeners on unmount
useEffect(() => {
return () => {
if (cyRef.current) {
cyRef.current.removeAllListeners();
cyRef.current = null;
}
};
}, []);
const cyCallback = useCallback(
(cy: CytoscapeRef) => {
// this is called each render of the component, don't add more listeners
if (cyRef.current) return;
cyRef.current = cy;
cy.ready(...);
cy.on(...);
},
[...dependencies],
);
return <Cytoscape cy={cyCallback} {...} />;
}
hmm ok useRef always feels a bit dirty to me like you're changing stuff that react wants to control, and it will bite you eventually...
@williaster your code works well for me!