简体   繁体   中英

Component doesn't re-render after I set state

I'm trying to build a mini project in react, it is a BMI tracker, the user enters his weight and height, then the program calculates the BMI and whenever the application receives data (height and weight) from the user it then represents the result and the date at which the user entered that data in a graph. For that, I made three components, the first is the InputContainer component inside of which the calculations are performed.

import React, { useState, useRef, useEffect } from 'react';
import Graph from '../Graph/Graph';
import Logs from '../Logs/logs';
import './InputContainer.scss';
let date;
let currentDate;
let dates = []
let bmiResults = []
let dataLog = [];
export default function InputContainer() {
    const [result, setBmiResult] = useState('');
    const [height, setHeight] = useState('');
    const [weight, setWeight] = useState('');
    const [bmiAxis,setBmiAxis] = useState('');
    const [dateAxis,setDateAxis] = useState('');
    const calculateButton = useRef(null);
    const firstUpdate = useRef(true);
    useEffect(() => {
        date = new Date();
        currentDate = `${date.getDate()}/${date.getMonth()}/${date.getFullYear()}`
        calculateButton.current.disabled = isNaN(parseFloat(height) && parseFloat(weight)) ? true : false;
    }, [height, weight]);

    useEffect(() => {
        if (firstUpdate.current) {
            firstUpdate.current = false;
            return
        }
        let dataLogElement = <div className="test">
                    <div className="bmi-and-date">
                        <p className="bmi">
                            BMI: {result}
                        </p>
                        <p className="date">
                            Date: {currentDate}
                        </p>
                    </div>
                    <div className="height-and-weight">
                        <p className="height">
                            Height: {height}
                        </p>
                        <p className="weight">
                            weight: {weight}
                        </p>
                    </div>
            </div>;
        dataLog.push(dataLogElement);
        dates.push(currentDate);
        bmiResults.push(result);
        setBmiAxis(bmiResults);
        setDateAxis(dates);
    }, [result])

    const calculate = () => {
        let bmi = (parseFloat(weight) / parseFloat(height) ** 2).toFixed(2)
        setBmiResult(parseFloat(bmi))
    }

    return (
        <>
            <div className='input-container'>
                <h1>BMI Tracker</h1>
                <div className="input">
                    <div className="height">
                        <h3>Enter Your Height in CM</h3>
                        <input type="number" onChange={(e) => {
                            setHeight(e.target.value)
                        }} value={height} />
                    </div>
                    <div className="weight">
                        <h3>Enter Your Weight in Kg</h3>
                        <input type="number" onChange={(e) => {
                            setWeight(e.target.value)
                        }} />
                    </div>
                </div>
                <button className='calculate' onClick={calculate} ref={calculateButton}>Calculate</button>
            </div>
            <Graph bmi={result} date={currentDate} dates={dateAxis} results={bmiAxis} />
            <Logs element = {dataLog}/>
        </>
    )
}

the second component is the Graph component which receives the user's data from props and then displays it in a graph:

import React from 'react'
import { Line } from 'react-chartjs-2';
import './Graph.scss'


export default function Graph(props) {
    const data = {
        labels: props.dates,
        datasets: [
            {
                label: 'BMI',
                data: props.results,
                fill: 'origin',
                backgroundColor: '#16a5e1',
                pointBackgroundColor: '#16a5e1',
                pointBorderColor: 'blue',
                pointRadius: '4'
            },
        ],
    };
    const options = {
        tension: 0.4,
        scales: {
            y: {
                ticks: {
                    color: 'white'
                },
                beginAtZero: true
            },
            x: {
                ticks: {
                    color: 'white'
                },
            }

        }
    };
    return (
        <div className='graph-container'>
            <Line data={data} options={options} />
        </div>
    )
}

Then the third component which is the Logs component, it displays the details of each user submission at the button of the page

import React from 'react'
import './logs.scss'
export default function Logs(props) {
    return (
        <div className='logs-container'>
            {props.element}
        </div>
    )
}

Once I start the app, I enter the weight and height then click the calculate button, the data gets plotted into the graph successfully and its corresponding details are logged at the bottom of the page, and this is exactly what I wanted, but the problem is that the data entered by the user gets displayed successfully only for the first entry, when I enter data a second time, calculations are done properly in the background and the results of the second entry are calculated, but nothing changes on the screen, and I can only see the results of the first entry, then once I start entering data for the third entry, I get the results of the first along with the second entry that I previously entered displayed on the screen, results only get displayed when I start entering data for the next entry and not immediately after clicking the calculate button. Here Is an example of what I want to achieve: 在此处输入图片说明

And Here is a link of the repo if you want to test it in your machine: https://github.com/Marouane328/Bmi-tracker

In Graph, can you consider converting const data and const options to states and use useEffect to assign the props values to the states giving props has dependency for useEffect.

try this it worked in your repo at codesandbox( https://codesandbox.io/s/peaceful-hofstadter-onso9 ):

const [data, setData] = React.useState({
    labels: props.dates,
    datasets: [
        {
            label: 'BMI',
            data: props.results,
            fill: 'origin',
            backgroundColor: '#16a5e1',
            pointBackgroundColor: '#16a5e1',
            pointBorderColor: 'blue',
            pointRadius: '4'
        },
    ],
});
const [options, setOptions] = React.useState({
    tension: 0.4,
    scales: {
        y: {
            ticks: {
                color: 'white'
            },
            beginAtZero: true
        },
        x: {
            ticks: {
                color: 'white'
            },
        }

    }
});

React.useEffect(
    ()=>{
        setData({
            labels: props.dates,
            datasets: [
                {
                    label: 'BMI',
                    data: props.results,
                    fill: 'origin',
                    backgroundColor: '#16a5e1',
                    pointBackgroundColor: '#16a5e1',
                    pointBorderColor: 'blue',
                    pointRadius: '4'
                },
            ],
        });
        setOptions({
            tension: 0.4,
            scales: {
                y: {
                    ticks: {
                        color: 'white'
                    },
                    beginAtZero: true
                },
                x: {
                    ticks: {
                        color: 'white'
                    },
                }
    
            }
        });
    },[props]
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM