How to reheat d3 force simulation in React.StrictMode?

Viewed 55

I have decided to create a interactive force directed graph in React using D3 but and everything is working for after the simulation started but the dragging doesn't work in React StrictMode I assume it must be due to the mounting and remount of component in ReactStrict mode 18 but I can't really pinpoint the reason.

Where is the content of my use effect hook

useEffect(function () {
      if (!svgRef.current) return;
      const svg = d3.select(svgRef.current);

      const nodes = d3.map(
        props.nodes,
        (node): Node => ({
          ...node,
        })
      );
      const links = d3
        .map(props.links, (link) => ({
          source: nodes.find((node) => node.id === link.source),
          target: nodes.find((node) => node.id === link.target),
        }))
        .filter(
          (link): link is { source: Node; target: Node } =>
            link.source !== undefined && link.target !== undefined
        );

      const simulation = d3
        .forceSimulation(nodes)
        .force(
          "link",
          d3
            .forceLink(links)
            .id((_, i) => nodes[i].id)
            .distance(LINK_LENGHT)
        )
        .force("charge", d3.forceManyBody().strength(-NODE_REPULSION))
        .force("center", d3.forceCenter())
        .force("forceX", d3.forceX().strength(NODE_GRAVITY))
        .force("forceY", d3.forceY().strength(NODE_GRAVITY))
        .force("colide", d3.forceCollide(NODE_RADIUS * NODE_MARGIN))
        .on("tick", ticked);

      const link = svg
        .selectAll("line")
        .data(links)
        .enter()
        .append("line")
        .style("stroke", "#aaa");

      const node = svg
        .selectAll("circle")
        .data(nodes)
        .enter()
        .append("circle")
        .call(
          d3
            .drag<SVGCircleElement, Node>()
            .on("start", dragstarted)
            .on("drag", dragged)
            .on("end", dragended)
        )
        .attr("fill", (node) => node.color))
        .attr("r", NODE_RADIUS)
        .attr("stroke", "#000000")
        .attr("stroke-width", 1);

      function ticked() {
        link
          .attr("x1", (d) => d.source.x!)
          .attr("y1", (d) => d.source.y!)
          .attr("x2", (d) => d.target.x!)
          .attr("y2", (d) => d.target.y!);

        node.attr("cx", (d) => d.x!).attr("cy", (d) => d.y!);
      }

      function dragstarted(event: any, d: Node) {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
      }

      function dragged(event: any, d: Node) {
        console.log(simulation.alpha());
        d.fx = event.x;
        d.fy = event.y;
      }

      function dragended(event: any, d: Node) {
        if (!event.active) simulation.alphaTarget(0.0001);
        d.fx = null;
        d.fy = null;
      }
    },
    [props.nodes.length, props.links.length]
)

Any clue or help is appreciated.

1 Answers

Due to it working after removing the StrictMode I speculated that i need a cleanup function inside my useEffect. Turns out that i just have to remove the links and nodes that I have inserted like so.

return () => {
    node.remove();
    link.remove();
}
Related