Home > Net >  Bar chart is not being updated when "untick" the checkboxes in React Hooks
Bar chart is not being updated when "untick" the checkboxes in React Hooks

Time:11-22

I have created a Bar chart in a react application. This chart is dynamic and by checking each checkbox a set of data needs to be uploaded.

Problem: data is being updated and displayed on the chart, however when I click on that specific checkbox nothing happens and chart does not get back to the original state. I tried Toggling the state of checkboxes but problem is still there and chart is not being updated.

This is my code:

import React, { useEffect, useState } from "react";
import { Bar } from "react-chartjs-2";

const RandomStatic = (props) => {
  let jsonData = [
    { gender: "female" },
    { gender: "male" },
    { gender: "female" },
    { gender: "male" },
    { gender: "female" },
    { gender: "male" },
    { gender: "female" },
    { gender: "female" },
    { gender: "female" },
    { gender: "female" },
  ];

  const [userFemaleCounter, setUserFemaleCounter] = useState();

  const [userMaleCounter, setUserMaleCounter] = useState();

  const [totalCounter, setTotalCounter] = useState();

  const [femalePercentage, setFemalePercentage] = useState();

  const [malePercentage, setMalePercentage] = useState();

  const [chartData, setChartData] = useState(jsonData);

  const [femaleIsChecked, setFemaleIsChecked] = useState(false);

  const [maleIsChecked, setMaleIsChecked] = useState(false);



 useEffect(() => {

    // Getting all Data in an array
    let allData = jsonData.map(function (e) {

      return e.gender;

    });
console.log(allData);
// Display Gender data on the chart
let fCounter = [];
let mCounter = [];
allData.forEach((gender) => {
  if (gender === "female") {
    fCounter =   fCounter;
  } else if (gender === "male") {
    mCounter =   mCounter;
  }
});


let userFemaleCounter = fCounter;
setUserFemaleCounter(fCounter);
console.log(userFemaleCounter);

let userMaleCounter = mCounter;
setUserMaleCounter(mCounter);
console.log(userMaleCounter);

let totalCounter = fCounter   mCounter;
setTotalCounter(totalCounter);
console.log(totalCounter);

let femalePercentage = (fCounter / totalCounter) * 100;
setFemalePercentage(femalePercentage);

let malePercentage = (mCounter / totalCounter) * 100;
setMalePercentage(malePercentage);


}, [chartData]);

useEffect(() => {
    ///change the jsonData
    //setChartData
    let allData = chartData.map(function (e) {
      return e.gender;
    });
    let mCounter = [];
    let fCounter = [];
    allData.forEach((gender) => {
      if (gender === "female") {
        fCounter =   fCounter;
      } else if (gender === "male") {
        mCounter =   mCounter;
      }
    });

 let totalCounter = fCounter   mCounter;
    setTotalCounter(totalCounter);

let userFemaleCounter = fCounter;
setUserFemaleCounter(userFemaleCounter);
let femalePercentage = (fCounter / totalCounter) * 100;
setFemalePercentage(femalePercentage);
let userMaleCounter = 0;
setUserMaleCounter(userMaleCounter);
let malePercentage = 0;
setMalePercentage(malePercentage);



}, [femaleIsChecked]);




//useeffect for MALE checkbox
  useEffect(() => {



let allData = jsonData.map(function (e) {
  return e.gender;
});
let mCounter = [];
let fCounter = [];
allData.forEach((gender) => {
  if (gender === "female") {
    fCounter =   fCounter;
  } else if (gender === "male") {
    mCounter =   mCounter;
  }
});

let totalCounter = fCounter   mCounter;
setTotalCounter(totalCounter);

let malePercentage = (mCounter / totalCounter) * 100;
setMalePercentage(malePercentage);

fCounter = [];
let femalePercentage = [];
setFemalePercentage(femalePercentage);


}, [maleIsChecked]);





const toggleFemale = () => {
  
    if(femaleIsChecked == true)
    {
      setFemaleIsChecked(false)
    }else{
      setFemaleIsChecked(true)
    }
    
  }


 const toggleMale = () => setMaleIsChecked(chartData)
  const toggleAll = () => setChartData(!chartData)
  //console.log(toggleFemale)



return (
    <div>
      <Bar
        className="chart"
        data={{
          labels: ["Female", "Male"],
          datasets: [
            {
              data: [femalePercentage, malePercentage],
              backgroundColor: ["green", "yellow"],
              borderColor: ["green", "yellow"],
              borderWidth: 0.5,
            },
          ],
        }}
    options={{
      responsive: true,
      maintainAspectRatio: false,
      plugins: {
        title: {
          display: true,
          text: "Male and Female Ratio",
        },
        legend: {
          display: false,
        },
      },
      scales: {
        y: {
          display: true,
          title: {
            display: true,
            text: "Percentage",
          },
        },
        x: {
          display: true,
          title: {
            display: true,
            text: "Population",
          },
        },
      },
      showTooltips: false,
      hover: false,
    }}
  />
  <div className="checkbox-form-wrapper">
    <div className="chartBox">
      <label>Female Only</label>
      <input
        name="femalePercentage"
        type="checkbox"
        //checked={femaleIsChecked}
        onChange={toggleFemale}
      />
      <label>Male Only</label>
      <input
        name="malePercentage"
        type="checkbox"
        checked={maleIsChecked}
        onChange={toggleMale}
        
      />
      <label>Both Female and Male</label>
      <input
        name="chartData"
        type="checkbox"
        checked={chartData}
        onChange={toggleAll}
      />
    </div>
  </div>
</div>


 );
};

export default RandomStatic;

Thank you :)

CodePudding user response:

In order to make the code above work as you would expect it, the main changes needed are:

  1. add guards in each Effect to run only when that flag is true. Example for maleIsChecked:

    if(!maleIsChecked) { return; }

  2. for the Effect displaying both, make sure it runs only if both flags are off:

    if(femaleIsChecked || maleIsChecked) { return;}

    and that it runs on every change

    useEffect(() => { ... }, [chartData, maleIsChecked, femaleIsChecked]):

  3. update toggle functions to switch "correctly". Example for toggleMale:

    const toggleMale = () => setMaleIsChecked(!maleIsChecked)

However, I strongly recommend to switch the approach from checkboxes to radio buttons. For me, this seems more natural as interaction, because you have all the options "on the table" and they are mutually exclusive. Working code suggested below, changes highlighted with CHANGE:

import React, { useEffect, useState } from "react";
import { Bar } from "react-chartjs-2";

// CHANGE: define an enum to handle data types to be displayed
const TypesOfDataToDisplay = {
    MALE: 'male',
    FEMALE: 'female',
    BOTH: 'both'
}

const RandomStatic = (props) => {
  let jsonData = [
    { gender: "female" },
    { gender: "male" },
    { gender: "female" },
    { gender: "male" },
    { gender: "female" },
    { gender: "male" },
    { gender: "female" },
    { gender: "female" },
    { gender: "female" },
    { gender: "female" },
  ];

  const [userFemaleCounter, setUserFemaleCounter] = useState();

  const [userMaleCounter, setUserMaleCounter] = useState();

  const [totalCounter, setTotalCounter] = useState();

  const [femalePercentage, setFemalePercentage] = useState();

  const [malePercentage, setMalePercentage] = useState();

  const [chartData, setChartData] = useState(jsonData);

  // CHANGE: define a new state that indicates data type to be displayed
  const [dataToDisplay, setDataToDisplay] = useState(TypesOfDataToDisplay.BOTH);

  // CHANGE: remove boolean states (using them, for each toggle you will generate two renderings)
  //const [femaleIsChecked, setFemaleIsChecked] = useState(false);
  //const [maleIsChecked, setMaleIsChecked] = useState(false);

  useEffect(() => {

    // CHANGE: skip this effect if not needed
    if(dataToDisplay !== TypesOfDataToDisplay.BOTH) {
        return;
    }

    // Getting all Data in an array
    let allData = jsonData.map(function (e) {

      return e.gender;

    });
    console.log(allData);
    // Display Gender data on the chart
    let fCounter = [];
    let mCounter = [];
    allData.forEach((gender) => {
      if (gender === "female") {
        fCounter =   fCounter;
      } else if (gender === "male") {
        mCounter =   mCounter;
      }
    });


    let userFemaleCounter = fCounter;
    setUserFemaleCounter(fCounter);
    console.log(userFemaleCounter);

    let userMaleCounter = mCounter;
    setUserMaleCounter(mCounter);
    console.log(userMaleCounter);

    let totalCounter = fCounter   mCounter;
    setTotalCounter(totalCounter);
    console.log(totalCounter);

    let femalePercentage = (fCounter / totalCounter) * 100;
    setFemalePercentage(femalePercentage);

    let malePercentage = (mCounter / totalCounter) * 100;
    setMalePercentage(malePercentage);


  }, [chartData, dataToDisplay]); // CHANGE: make sure the effect is called on data type change

  useEffect(() => {

    // CHANGE: skip this effect if not needed
    if(dataToDisplay !== TypesOfDataToDisplay.FEMALE) {
        return;
    }

    ///change the jsonData
    //setChartData
    let allData = chartData.map(function (e) {
      return e.gender;
    });
    let mCounter = [];
    let fCounter = [];
    allData.forEach((gender) => {
      if (gender === "female") {
        fCounter =   fCounter;
      } else if (gender === "male") {
        mCounter =   mCounter;
      }
    });

    let totalCounter = fCounter   mCounter;
    setTotalCounter(totalCounter);

    let userFemaleCounter = fCounter;
    setUserFemaleCounter(userFemaleCounter);
    let femalePercentage = (fCounter / totalCounter) * 100;
    setFemalePercentage(femalePercentage);
    let userMaleCounter = 0;
    setUserMaleCounter(userMaleCounter);
    let malePercentage = 0;
    setMalePercentage(malePercentage);



  }, [dataToDisplay]); // CHANGE: make sure the effect is called on data type change




  //useeffect for MALE checkbox
  useEffect(() => {

    // CHANGE: skip this effect if not needed
    if(dataToDisplay !== TypesOfDataToDisplay.MALE) {
        return;
    }

    let allData = jsonData.map(function (e) {
      return e.gender;
    });
    let mCounter = [];
    let fCounter = [];
    allData.forEach((gender) => {
      if (gender === "female") {
        fCounter =   fCounter;
      } else if (gender === "male") {
        mCounter =   mCounter;
      }
    });

    let totalCounter = fCounter   mCounter;
    setTotalCounter(totalCounter);

    let malePercentage = (mCounter / totalCounter) * 100;
    setMalePercentage(malePercentage);

    fCounter = [];
    let femalePercentage = [];
    setFemalePercentage(femalePercentage);


  }, [dataToDisplay]); // CHANGE: make sure the effect is called on data type change

  // CHANGE: remove togglers for the boolean states
  /*const toggleFemale = () => {
  
    if(femaleIsChecked == true)
    {
      setFemaleIsChecked(false)
    }else{
      setFemaleIsChecked(true)
    }
    
  }


 const toggleMale = () => setMaleIsChecked(chartData)
  const toggleAll = () => setChartData(!chartData)*/

// CHANGE: add only one handler for changing data to be displayed
const toggleDisplay = (event) => {
    setDataToDisplay(event.currentTarget.value);
}

    // CHANGE: switch from checkboxes to radio buttons and update definitions with new handler
  return (
    <div className="chartContainer">
      <Bar
        className="chart"
        data={{
          labels: ["Female", "Male"],
          datasets: [
            {
              data: [femalePercentage, malePercentage],
              backgroundColor: ["green", "yellow"],
              borderColor: ["green", "yellow"],
              borderWidth: 0.5,
            },
          ],
        }}
        options={{
          responsive: true,
          maintainAspectRatio: false,
          plugins: {
            title: {
              display: true,
              text: "Male and Female Ratio",
            },
            legend: {
              display: false,
            },
          },
          scales: {
            y: {
              display: true,
              title: {
                display: true,
                text: "Percentage",
              },
            },
            x: {
              display: true,
              title: {
                display: true,
                text: "Population",
              },
            },
          },
          showTooltips: false,
          hover: false,
        }}
      />
      <div className="checkbox-form-wrapper">
        <div className="chartBox">
          <label>Female Only</label>
          <input
            name="femalePercentage"
            type="radio"
            value={TypesOfDataToDisplay.FEMALE}
            checked={dataToDisplay === TypesOfDataToDisplay.FEMALE}
            onChange={toggleDisplay}
          />
          <label>Male Only</label>
          <input
            name="malePercentage"
            type="radio"
            value={TypesOfDataToDisplay.MALE}
            checked={dataToDisplay === TypesOfDataToDisplay.MALE}
            onChange={toggleDisplay}

          />
          <label>Both Female and Male</label>
          <input
            name="chartData"
            type="radio"
            value={TypesOfDataToDisplay.BOTH}
            checked={dataToDisplay === TypesOfDataToDisplay.BOTH}
            onChange={toggleDisplay}
          />
        </div>
      </div>
    </div>


  );
};

export default RandomStatic;
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related