vasturiano / react-force-graph

React component for 2D, 3D, VR and AR force directed graphs
https://vasturiano.github.io/react-force-graph/example/large-graph/
MIT License
2.17k stars 277 forks source link

d3Force is not a function #481

Open egorairo opened 9 months ago

egorairo commented 9 months ago

Hi! I'm trying to use d3force method but have error "d3Force is not a function". Could you please explain me why I am getting the error in the code below.

import React, { useEffect, useRef } from 'react';
import dynamic from 'next/dynamic';

const ForceGraph2D = dynamic(() => import('react-force-graph-2d'), {
  ssr: false,
});

const data = {
  nodes: [{ id: 'A' }, { id: 'B' }, { id: 'C' }, { id: 'D' }],
  links: [
    { source: 'B', target: 'C', value: 8 },
    { source: 'C', target: 'D', value: 10 },
    { source: 'D', target: 'A', value: 6 },
    { source: 'B', target: 'A', value: 6 },
    { source: 'B', target: 'D', value: 6 },
    { source: 'D', target: 'D', value: 6, curvature: 0.3 },
  ],
};

export default function App() {
  const forceRef = useRef(null);

  useEffect(() => {
    forceRef.current.d3Force('charge').strength(-400);
  });

  return (
    <ForceGraph2D
      graphData={data}
      nodeLabel="id"
      linkCurvature="curvature"
      enablePointerInteraction={true}
      linkDirectionalParticleWidth={1}
      ref={forceRef}
    />
  );
}
vasturiano commented 9 months ago

@egorairo you need to check whether your ref is defined before using it. It seems like in this case it is not. Specifically when you're using SSR this is quite important as refs tend to be defined only on the client side.

samjusaitis commented 8 months ago

@egorairo I ran into a similar problem using Next.js and trying to dynamically import this package.

Couple things that got it working for me:

  1. Had to ensure I was using a workaround like this one to ensure the ref was getting properly attached to the force graph.
  2. Even with this workaround, the ref wasn't available on the first render (unsure why). So I added further logic to check for this:
    
    'use client';

import { useEffect, useReducer, useRef, useState } from 'react'; import { forceY } from 'd3-force'; import ForceGraph2D from './ForceGraph2D';

export const Graph = props => { const { data } = props;

const graphRef = useRef(null);

const [hasInitialisedForces, setHasInitialisedForces] = useState(false);
const [tick, incrementTick] = useReducer(v => v + 1, 0);

useEffect(() => {
    const graph = graphRef.current;

    if (!graph || hasInitialisedForces) return;

    graph.d3Force('center', null);
    graph.d3Force('directional', forceY(100));

    setHasInitialisedForces(true);
}, [tick, hasInitialisedForces]);

return (
    <ForceGraph2D
        ref={graphRef}
        graphData={data}
        onEngineTick={incrementTick}
    />
);

};

anyuruf commented 2 weeks ago

    useEffect(() => {
        if (fgRef.current) {
            fgRef.current.d3Force(
                'link',
                forceLink()
                    .id((d: any) => {
                        return d.id;
                    })
                    .distance(24)
                    .strength(1),
            );
        }
    }, []);```
You need to wrap it in a use affect so it only runs on the client. Had the issue because remix would try to run the code on the server and the useEffect helps