javascriptreactjsd3.jsreact-hooksbrush

Cannot do a brush interaction and set a state in a useEffect hook with D3 and React


I'm trying to create a brush interaction on a timeline with D3 and React but I can't figure out why it doesn't work.

Everything seems fine but when I set a state within the useEffect() the index variable creates an infinite loop.

    const Timeline = () => {
    const svgRef = useRef(null!);
    const brushRef = useRef(null!);
    const x = scaleLinear().domain([0, 1000]).range([10, 810]);
    const [pos, setPos] = useState([]);

    useEffect(() => {
        const svg = select(svgRef.current);

        svg.select('.x-axis')
            .attr('transform', `translate(0,${110})`)
            .call(axisBottom(x));

        const brush = brushX()
            .extent([
                [10, 10],
                [810, 110],
            ])
            .on('start brush end', function () {
                const nodeSelection = brushSelection(
                    select(brushRef.current).node(),
                );
                const index = nodeSelection.map(x.invert);

                setPos(index);
            });

        console.log(pos);

        select(brushRef.current).call(brush).call(brush.move, [0, 100].map(x));
    }, [pos]);

    return (
        <svg ref={svgRef} width="1200" height={800}>
            <rect x={x(200)} y={10} width={400} height={100} fill={'blue'} />
            <g className="x-axis" />
            <g ref={brushRef} />
            <text>{pos[0]}-{pos[1]}</text>
        </svg>
    );
};

Any ideas to fix that? Thanks!


Solution

  • Seems that this solves the problem:

    const runBrushes = useCallback(async () => {
        const brush = brushX()
            .extent([
                [10, 10],
                [810, 110],
            ])
            .on('start brush end', function () {
                const nodeSelection = brushSelection(
                    select(brushRef.current).node(),
                );
                const index = nodeSelection.map(x.invert);
    
                setPos(index);
            });
    
        select(brushRef.current).call(brush).call(brush.move, [0, 100].map(x));
    }, [pos, x]);
    
    useEffect(() => {
        const svg = select(svgRef.current);
    
        svg.select('.x-axis')
            .attr('transform', `translate(0,${110})`)
            .call(axisBottom(x));
    
        runBrushes().catch(console.error);
    }, []);