Matteo
Matteo

Reputation: 37

React component don't update the state properly with fetched data

I have these two components: I fetch the data in the parent component and then pass it to the child component, which formats it into an object so that it can be used in the construction of a graph.

When the child component is mounted for the first time, the graph is not rendered. If I try to change the child component code to update it (e.g. delete linkColor from ForceGraph2D’s props), the graph will be displayed correctly. I want to find a way to see it right away, the first time the component is mounted.

Parent component:

import React, {Fragment, useEffect, useState} from "react";
import AddressList from "./AddressList";
import ClusterGraph from "./ClusterGraph";
import axios from "axios";

function ClusterPage(props) {
    const [cluster, setCluster] = useState([]);
    const [subCluster, setSubCluster] = useState([]);

    useEffect(() => {
        fetchData1();
        fetchData2();
    },[]);

    const fetchData1 = async () => {
        const address = props.match.params.addressHash;
        const response = await axios.get('http://localhost:5000/', {
            params: {address: address}
        });
        const data = await response.data;
        setCluster(data);
    }

    const fetchData2 = async () => {
        const address = props.match.params.addressHash;
        const response = await axios.get('http://localhost:5000/sub/', {
            params: {address: address}
        });
        const data = await response.data;
        setSubCluster(data);
    }

    return (
        <div key={props.match.params.id}>
            <Fragment>
                <AddressList data={cluster} />
                <ClusterGraph data1={cluster} data2={subCluster} />
            </Fragment>
        </div>
    );
}

export default ClusterPage;

Child component:

import '../styles/ClusterGraph.css';
import ForceGraph2D from 'react-force-graph-2d';
import React from "react";

class MyClusterGraph extends React.Component {
    constructor(props) {
        super(props);
        this.state = {nodes:[],links:[]};
    }

    componentWillMount() {
        this.loadData();
    }

    loadData = async () => {
        let nodes = this.props.data1.map(row => {
            let id = row.address_id;
            let addressHash = row.address_hash;
            let nodeColor;
            if(row.miner_address)
                nodeColor="blue";
            else
                nodeColor="purple";
            return {id:id,addressHash:addressHash,nodeColor:nodeColor};
        });
        let links = this.props.data2.map(row => {
            let source = row.address_id_1;
            let target = row.address_id_2;
            let linkColor;
            switch (row.link_type) {
                case 0:
                    linkColor="green";
                    break;
                case 1:
                    linkColor="red";
                    break;
                case 2:
                    linkColor="cyan";
                    break;
            }
            return {source:source,target:target,linkColor:linkColor};
        });
        this.setState({nodes:nodes,links:links});
    }

    render() {
        return (
            <div className="graph">
                <ForceGraph2D
                    graphData={this.state}
                    backgroundColor="white"
                    height={400}
                    width={700}
                    nodeLabel="addressHash"
                    nodeColor="nodeColor"
                    linkColor="linkColor" />
            </div>
        );
    }
}

function ClusterGraph({data1,data2}) {
    return (
        <div className="section2">
            <MyClusterGraph data1={data1} data2={data2} />
        </div>
    );
}

export default ClusterGraph;

Upvotes: 1

Views: 125

Answers (2)

Akhil Raghav
Akhil Raghav

Reputation: 343

Try making your parent component calls synchronous currently it is sync individually but it calls in async fashion change your code base accordingly

Upvotes: 0

Youssouf Oumar
Youssouf Oumar

Reputation: 46073

You could make sure you render your graphs after the data is fully fetched, and show a loader in the meantime, like so:

function ClusterPage(props) {
  const [cluster, setCluster] = useState([]);
  const [subCluster, setSubCluster] = useState([]);

  useEffect(() => {
    fetchData1();
    fetchData2();
  }, []);

  const fetchData1 = async () => {
    const address = props.match.params.addressHash;
    const response = await axios.get("http://localhost:5000/", {
      params: { address: address },
    });
    const data = await response.data;
    setCluster(data);
  };

  const fetchData2 = async () => {
    const address = props.match.params.addressHash;
    const response = await axios.get("http://localhost:5000/sub/", {
      params: { address: address },
    });
    const data = await response.data;
    setSubCluster(data);
  };
  if (cluster.length <= 0 || subCluster.length <= 0) {
    return <p>Loading...</p>;
  }
  return (
    <div key={props.match.params.id}>
      <Fragment>
        <AddressList data={cluster} />
        <ClusterGraph data1={cluster} data2={subCluster} />
      </Fragment>
    </div>
  );
}

This way, you would use the constructor to format your data, as componentWillMount() is deprecated and considered unsafe:

class MyClusterGraph extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      nodes: this.props.data1.map((row) => {
        let id = row.address_id;
        let addressHash = row.address_hash;
        let nodeColor;
        if (row.miner_address) nodeColor = "blue";
        else nodeColor = "purple";
        return { id: id, addressHash: addressHash, nodeColor: nodeColor };
      }),
      links: this.props.data2.map((row) => {
        let source = row.address_id_1;
        let target = row.address_id_2;
        let linkColor;
        switch (row.link_type) {
          case 0:
            linkColor = "green";
            break;
          case 1:
            linkColor = "red";
            break;
          case 2:
            linkColor = "cyan";
            break;
        }
        return { source: source, target: target, linkColor: linkColor };
      }),
    };
  }

  render() {
    return (
      <div className="graph">
        <ForceGraph2D
          graphData={this.state}
          backgroundColor="white"
          height={400}
          width={700}
          nodeLabel="addressHash"
          nodeColor="nodeColor"
          linkColor="linkColor"
        />
      </div>
    );
  }
}

function ClusterGraph({ data1, data2 }) {
  return (
    <div className="section2">
      <MyClusterGraph data1={data1} data2={data2} />
    </div>
  );
}

export default ClusterGraph;

Upvotes: 1

Related Questions