SoulHokib
SoulHokib

Reputation: 23

React component not re-rendering with useEffects

I am currently creating a front-end app in order to display some results of ML, and using React + Recharts. My version of React is 16.11.0, and version of recharts is 1.8.5.

Every time I update my data (after every iteration), I want to update the chart. So it first displays 30 points, that is the comportment intended, but when I add my 30 new points, pushing the array used to display data to 60, it looks like my component is not re-rendering, and only displays 30 points.

All the logic seems fine : on my main page side, the aggregation of data is correctly done, and the component receives the updated data correctly. But my chart still won't update.

Maybe am I doing things wrongly with the hooks, since I'm still a beginner. Thanks for your help.

The main page code, with the part of "simulateLocal" managing the data :

    import React, { useEffect, useState } from "react";
import { TextField, Button } from "@material-ui/core/";
import Grid from "@material-ui/core/Grid";
import CardContent from "@material-ui/core/CardContent";
import Box from "@material-ui/core/Box";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/core/styles";
import Chart from "./Charts";

let results = []
let params = [];

const useStyles = makeStyles({
  root: {
    maxWidth: 400
  },
  title: {
    fontSize: 20
  }
});

async function initializeState(e, contract, accounts) {
  await fetch("/init")
    .then(res => res.json())
    .then(data => {
      console.log(data);
    })
    .catch(console.log);
}

async function simulateLocal(e, contract, accounts, setCount, count, setResult, result) {
  //e.preventDefault();
  console.log("Local stochastic gradient descent");

  await contract.methods.resetState().send({ gas: 5000000, from: accounts[0] });

  await fetch("/run_stage")
    .then(res => res.json())
    .then(data => {
      params = data;
    })
    .catch(console.log);
  console.log(contract);
  await fetch("/get_results")
    .then(res => res.json())
    .then(data => {
      if(results.length == 0 ){
        results = { results : []}
      }
      data.results.forEach(result => {
        result.name = (parseInt(result.name, 10) + results.results.length).toString();
      })  
      results.results.push(...data.results);
      setResult(results)
    });
    console.log(results);
  setCount(count + 1);
}

async function simulateFederated(e, contract, accounts) {
  e.preventDefault();
  console.log("Federation happening.");
  console.log(params);

  for (var i = 0; i < 4; i++) {
    await contract.methods
      .store_params(params.weights[i])
      .send({ from: accounts[i + 1], gas: 5000000 });
  }

  const aggres = await contract.methods
    .run_agg()
    .send({ gas: 5000000, from: accounts[0] });
  console.log("runagg:" + aggres);

  const weight_res = await contract.methods
    .read_params()
    .call({ gas: 500000000, from: accounts[0] });
  console.log(weight_res);

  for (var i = 0; i < 4; i++) {
    await contract.methods
      .store_params(params.biases[i])
      .send({ from: accounts[i + 1], gas: 5000000 });
  }

  const aggres2 = await contract.methods
    .run_agg()
    .send({ gas: 500000000, from: accounts[0] });
  console.log("runagg:" + aggres2);

  const bias_res = await contract.methods
    .read_params()
    .call({ gas: 500000000, from: accounts[0] });
  console.log(bias_res);

  let postres = [];

  await fetch("/post_params", {
    method: "POST",
    body: JSON.stringify({
      weights: JSON.stringify(weight_res),
      biases: JSON.stringify(bias_res)
    })
  })
    .then(res => res.json())
    .then(data => {
      postres = data;
    });
  console.log(postres);
}

const ClientField = props => {
  const [count, setCount] = useState(0);
  const [result, setResult] = useState(results);
  const classes = useStyles();

  return (
    <Box border={0} borderColor="grey.500">
      <CardContent>
        <Typography className={classes.header} variant="h6">
          Simulation Panel
        </Typography>
      </CardContent>

      <CardContent>
        <Button
          variant="contained"
          size="small"
          style={{
            maxWidth: "100px",
            maxHeight: "30px",
            minWidth: "100px",
            minHeight: "30px",
            margin: "10px"
          }}
          onClick={e =>
            simulateLocal(e, props.instance, props.accounts, setCount, count, setResult, result)
          }
        >
          Run Local
        </Button>

        <Button
          variant="contained"
          size="small"
          style={{
            maxWidth: "100px",
            maxHeight: "30px",
            minWidth: "100px",
            minHeight: "30px",
            margin: "10px"
          }}
          onClick={e => simulateFederated(e, props.instance, props.accounts)}
        >
          Federate
        </Button>

        <Button
          variant="contained"
          size="small"
          style={{
            maxWidth: "100px",
            maxHeight: "30px",
            minWidth: "100px",
            minHeight: "30px",
            margin: "10px"
          }}
          onClick={e => initializeState(e, props.instance, props.accounts)}
        >
          Initialize
        </Button>
      </CardContent>
      <CardContent
        style={{
          margin: "auto",
          minHeight: "300px",
          maxHeight: "300px",
          maxWidth: "400px"
        }}
      >
        <Chart data={result} count={count}></Chart>
      </CardContent>
    </Box>
  );
};

export default ClientField;

And the component Charts, using the Recharts lib.

import React, { useState, useEffect, useDeepCompareEffect } from "react";
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend
} from "recharts";
const Results = ({ data, count }) => {
  const [result, setResult] = useState();
  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    console.log("charts props:", data.results);
    setResult(data.results)
    console.log("results updated")
  });


  return (
    <LineChart
      width={500}
      height={300}
      data={result}
      margin={{
        top: 5,
        right: 30,
        left: 20,
        bottom: 5
      }}
    >
      <CartesianGrid strokeDasharray="3 3" />
      <XAxis dataKey="name" />
      <YAxis />
      <Tooltip />
      <Legend />
      <Line
        type="monotone"
        dataKey="uv"
        stroke="#8884d8"
        activeDot={{ r: 8 }}
      />
    </LineChart>
  );
};

export default Results;

Upvotes: 2

Views: 5256

Answers (2)

oozywaters
oozywaters

Reputation: 1241

  1. React components compare state and props with previous values and rerender when they are changed. Whereas scalar variables (strings, numbers) are compared by values, objects (including arrays) are compared by reference. That said, when results.results.push() is performed in simulateLocal, the result reference remains the same and setResult(results) doesn't trigger rerender. You need to pass a new array, like this: setResult([...results]).
  2. Your Results component doesn't need useEffect hook, you can pass data.results prop directly into LineChart:
const Results = ({ data, count }) => {
  return (
    <LineChart data={data.result}>
      {/* ... */}
    </LineChart>
  );
};

Upvotes: 1

Amit Chauhan
Amit Chauhan

Reputation: 6899

In hooks if you update state with same value your component doesn't re-render. If you want to re-render then you need to create new array or object.

if data.results is array then do

setResults([...data.results]);

if data.results is object then do

setResults({...data.results});

Upvotes: 1

Related Questions