Home > Software design >  React why re-rendering changes the status of "data" from null to filled
React why re-rendering changes the status of "data" from null to filled

Time:10-07

I'm still learning React and I think this is the first time I've encountered such a problem.

First of all, I'm use a library called D3.js or D3-force. I just started to use it, I don't master it at all to be honest.

I'm trying to "display" circles in an svg using data received through an axios query.

Here is my code, (very simple) :

import React, {useRef, useEffect, useState} from 'react'
import {select} from "d3"
import axios from 'axios';

// const dummyData = [15, 20, 25, 30]

export default function D3() {

    const svgRef = useRef();
    const [data, setData] = useState(null)

    const getData = () => {
        axios.get('/api/carto')
        .then((response) => {
            setData(response.data);
        })
    }

    const monSVG = () =>  {
        const svg = select(svgRef.current);
        svg.append("g")
            .attr("class", "nodes")
            .selectAll("g")
            .data(data.nodes)
        
        svg.append("circle")
            .attr("r", 15)
            .attr("fill", "red")
            .attr("stroke", 1.5);
    }



    useEffect(() => {
        getData();

        if(data != null){
            monSVG();
        }
    }, [])

    return (
        <div>
            <p>D3 Exemple</p>
                <svg ref={svgRef}>
            </svg>
        </div>
    )
}

And a console.log of my data :

enter image description here

Sorry for the image.

I will now try to explain my problem as best I can :

I'm sure the axios call works, it returns me the data well. Only, during the useEffect, my useState data is null (I had to put a condition (if) to prevent my page from crashing completely).

I could see that a refresh (during a modification in the file, not an F5) changes the status of "data". After a re-render it is filled this time.

I think I don't know enough about the useEffect to understand what's going on. And it would be nice to have an explanation.

Don't worry, I already checked the official website of React and more.

Thanks for any information / help !

CodePudding user response:

You can just move the entire monSvg() method inside the useEffect like below

import React, { useEffect, useRef } from "react";
import { select } from "d3";
import axios from "axios";

export default function App() {
  const svgRef = useRef();
  useEffect(() => {
    const svg = select(svgRef.current);
    const getData = () => {
      axios.get("/api/carto").then((response) => {
        const { data } = response;
        svg.append("g").attr("class", "nodes").selectAll("g").data(data.nodes);
        svg
          .append("circle")
          .attr("r", 15)
          .attr("fill", "red")
          .attr("stroke", 1.5);
      });
    };
    getData();
  }, []);

  return (
    <div>
      <p>D3 Exemple</p>
      <svg ref={svgRef}></svg>
    </div>
  );
}

CodePudding user response:

The useEffect hook is basically a function that is called right after the component has begun a new lifecycle (not unlike ngOnInit in Angular).

In your case, you have a callback function and an empty array in your useEffect function which causes the useEffect to only be called once after component initialization, more on that here: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

I am not sure, if a useEffect hook is the best way to fill your state with data from an axios call, as the promise from that call may not be fulfilled yet, when the useEffect triggers. Please try to call the axios function in the useEffect too and set the state after its Promise is fulfilled:

axios.get('/api/carto')
        .then((response) => {
            setData(response.data);
            monSVG();
        })
  • Related