I have been doing a project where I present a graph on the screen. Now, the data
state within the App.js is being passed as a prop and then causes an infinite loop error
.
If the data variable is within the Graph.js file and used and defined within the useState
, then there is no issue.
I was a bit uncertain why this was happening as I am relatively new to react and javascript.
The goal is to add the data to the graph, it loads, then when I change the data within the App.js
file, the page will automatically load again with the new data.
Any help/advice will be much appreciated 🙂
App.js
function App() { const [data, setData] = useState(null) setData( { nodes:[ {name:"Max"}, {name:"George"}, {name:"Jesus"}, {name:"Ben"}, {name:"James"}, {name:"Sam"}, {name:"Sassms"} ], edges:[ {source:"Max", target:"George"}, {source:"George", target:"Jesus"}, {source:"Jesus", target:"Max"}, {source:"Jesus", target:"Ben"}, {source:"James", target:"Ben"}, {source:"Sam", target:"Sassms"}, {source:"Sam", target:"Ben"} ] }) console.log(data) return ( <Graph colour="Grey" data={data} setData={setData}/> ); }
Graph.js
function Graph(props) { const svgRef = useRef(null); useEffect( () => { let svg = d3.select(svgRef.current) if(svgRef.current) {// Check that svg element has been rendered let width = svg.attr("width") let height = svg.attr("height") d3.selectAll("svg > *").remove(); let edge = svg .append("g") .selectAll("line") .data(props.data.edges) .enter() .append("line") .attr("stroke-width", function(d) { return 3; }) .style("stroke", "black") let node = svg .append("g") .selectAll("circle") .data(props.data.nodes) .enter() .append("circle") .attr("r", 7) .attr("fill", function(d) { return "grey"; }) .attr("stroke", "black") } }, [props.data]) return ( <svg ref={svgRef} id="svgID" width="1000" height="900"></svg> ) }
Advertisement
Answer
It’s because you’re updating the state on every render again and again. If you just simply call setData()
inside the function, it will call this setter that invokes a re-render and triggers React to call your App
function again.
If you want to have a default state, you can do it like below. If you want to update the state, it should be done by other actions, like the click of a button, fetching some data, etc.
Check out the demo below where I added a button that adds a new name to your nodes
array:
function App() { const [data, setData] = React.useState({ nodes: [ { name: 'Max' }, { name: 'George' }, { name: 'Jesus' }, { name: 'Ben' }, { name: 'James' }, { name: 'Sam' }, { name: 'Sassms' }, ], edges: [ { source: 'Max', target: 'George' }, { source: 'George', target: 'Jesus' }, { source: 'Jesus', target: 'Max' }, { source: 'Jesus', target: 'Ben' }, { source: 'James', target: 'Ben' }, { source: 'Sam', target: 'Sassms' }, { source: 'Sam', target: 'Ben' }, ], }); return <Graph colour="Grey" data={data} setData={setData} />; } function Graph(props) { function addRandomName() { props.setData({ ...props.data, nodes: [ ...props.data.nodes, { name: Math.random().toString(36).slice(2) }, ], }); } return ( <React.Fragment> <button onClick={addRandomName}>Add new name</button> <h2>Names</h2> <p> {props.data.nodes.map((d) => d.name).join(', ')} </p> <h2>State</h2> <pre>{JSON.stringify(props.data, null, 2)}</pre> </React.Fragment> ); } ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
<div id="root"></div> <script src="https://unpkg.com/react/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>