How to sync ChartJS piechart with a dynamically updating text?

54 Views Asked by At

I have a textarea element which uses useState hook to remember what user has written in the textarea

const [textval,setTextval]=useState("");

Inside the return() of TextForm.js i have

<textarea 
        className="form-control" 
        id="exampleFormControlTextarea1" rows="8" 
        value={textval}
        onChange={update}></textarea>
        //and more code...

I am implementing a pie chart using chart.js in react. Piechart shows vowels, consonants and numbers in the wriiten text

const data = {
        labels: ["Vowels", "Consonants", "Numbers"],
        datasets: [
          {
            label: "Frequency",
            data: piedata,
            backgroundColor: [
              "#244D70",
              "#F7E018",
              "#FE452A"
            ],
            borderColor: [
              "rgba(255,99,132,1)",
              "rgba(54, 162, 235, 1)",
              "rgba(255, 206, 86, 1)"
            ],
            borderWidth: 1
          }]
      };
const ctx=document.getElementById("mydiv");
    let piechart;
    useEffect(()=>{
            piechart=new Chart(ctx,{
                type:'pie',
                data: data
            });
            return ()=>{piechart.destroy()}
        },[textval])

data in the above code is also updated using a hook piedata, piedata is updated with the hook textval in a function called update() which is called when onchange() is triggered in textarea properties Code for update is below:

function update(event)
    {
        const newtext=event.target.value;
        
        const vowelRegex = /[aAeEiIoOuU]/gi; 
        const vowels=textval.match(vowelRegex);
        let vowelCount = vowels?vowels.length:0;
        const consoRegex=/[bBcCdDfFgGhHjJkKlLmMnNpPqQrRsStTvVwWxXyYzZ]/gi;
        const consonants=textval.match(consoRegex);
        let consonantCount=consonants?consonants.length:0;
        const numberRegex=/[1234567890]/gi;
        const numbers=textval.match(numberRegex);
        let numberCount=numbers?numbers.length:0;
        setpiedata([vowelCount,consonantCount,numberCount]);
        setTextval(newtext);

    }

Above code is helping us re-render the pie chart every time user types something new in the textarea The only problem is that the piechart is 1 letter out of sync with the textarea. Like, if the user types 'e' then that 'e' is counted in the piechart only after user types one more character. Piechart is using the values of previous textval than the current textval.

** I will attach whole code below. This component is being rendered inside index.js**

import {useEffect, useState} from 'react';
import 'chart.js/auto';
import {Chart} from "chart.js";

function TextForm(props) {
    const [piedata,setpiedata]=useState([0,0,0]);
    const [textval,setTextval]=useState("");
    //Below variable helps user show/hide the pie chart
    const [showpie,setPie]=useState(true);
    const data = {
        labels: ["Vowels", "Consonants", "Numbers"],
        datasets: [
          {
            label: "Frequency",
            data: piedata,
            backgroundColor: [
              "#244D70",
              "#F7E018",
              "#FE452A"
            ],
            borderColor: [
              "rgba(255,99,132,1)",
              "rgba(54, 162, 235, 1)",
              "rgba(255, 206, 86, 1)"
            ],
            borderWidth: 1
          }]
      };
    
    function update(event)
    {
        const newtext=event.target.value;
        setTextval(newtext);
        const vowelRegex = /[aAeEiIoOuU]/gi; 
        const vowels=textval.match(vowelRegex);
        let vowelCount = vowels?vowels.length:0;
        const consoRegex=/[bBcCdDfFgGhHjJkKlLmMnNpPqQrRsStTvVwWxXyYzZ]/gi;
        const consonants=textval.match(consoRegex);
        let consonantCount=consonants?consonants.length:0;
        const numberRegex=/[1234567890]/gi;
        const numbers=textval.match(numberRegex);
        let numberCount=numbers?numbers.length:0;
        setpiedata([vowelCount,consonantCount,numberCount]);

    }
    const ctx=document.getElementById("mydiv");
    //useeffect updates on each updation of textval state.
    // And rerenders the pie chart. On returning it destroys 
    //the pie chart befor new pie chart is rendered again on the updation
    // of Textval, thus removing the error of *canvas not destroyed*
    //WHAT WE ARE DOING HERE? WE ARE RERENDERING THE PIE CHART ON EACH UPDATION OF TEXT
    //IN THE TEXTAREA, That's why we gave textval as dependency
    let piechart;
    useEffect(()=>{
            setTimeout(() => {
                piechart=new Chart(ctx,{
                    type:'pie',
                    data: data
                });
                return ()=>{piechart.destroy()}
            }, 100);
            
        },[textval])
    function handleclick()
    {
        const newtext=textval.toUpperCase();
        setTextval(newtext);
    }
    function handlelowclick()
    {
        const newtext=textval.toLowerCase();
        setTextval(newtext);
    }
    function updatepie()
    {
        if(showpie===true)
        {
            setPie(false);
        }
        else{setPie(true);}
    }
  return (
    <div>
        <h1>{props.heading}</h1>
        <div className="mb-3">
        <textarea 
        className="form-control" 
        id="exampleFormControlTextarea1" rows="8" 
        value={textval}
        onChange={update}></textarea>
        </div>
        <button className="btn btn-success mx-2 my-1" onClick={handleclick}>Convert to Uppercase</button>
        <button className="btn btn-success mx-2 my-1" onClick={handlelowclick}>Convert to Lowercase</button>
        <button className="btn btn-info mx-2" onClick={updatepie}>Show Stats</button>
        <div className="container my-3">
            <h2>Your Summary is:</h2>
            <p>{textval.length===0?0:textval.split(" ").length} words {textval.length} characters</p>
            <p>{0.008*(textval.length===0?0:textval.split(" ").length)} minutes to read</p>
            <div style={{height:"60vh",position:"relative", marginBottom:"1%", padding:"1%",display:{showpie}?"block":"none"}}>
                <canvas id="mydiv"></canvas></div>
            <h3>Preview</h3>
            <p>{textval}</p>
        </div>
    </div>
  );
}
export default TextForm;

PICS OF THE ISSUE:

On adding text="a"

After deleting the character I get that character in chart; on deleting "a"

I tried setting setTimeout inside useeffect but it gave me error that Cannot read properties of null (reading 'id') TypeError: Cannot read properties of null (reading 'id') at new Chart (http://localhost:3000/static/js/bundle.js:48963:169) I am new to react and was trying to implement a dynamically updating chart. Only problem i am getting is that piehart is 1 character out of sync.

0

There are 0 best solutions below