【问题标题】:Component doesn't re-render after I set state设置状态后组件不会重新渲染
【发布时间】:2023-03-09 18:08:01
【问题描述】:

我正在尝试在 react 中构建一个迷你项目,它是一个 BMI 跟踪器,用户输入他的体重和身高,然后程序计算 BMI 并且每当应用程序从用户那里收到数据(身高和体重)时,它然后表示结果和用户在图表中输入该数据的日期。为此,我制作了三个组件,第一个是 InputContainer 组件,在其中执行计算。

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}/>
        </>
    )
}

第二个组件是Graph 组件,它从 props 接收用户的数据,然后将其显示在图表中:

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>
    )
}

然后是第三个组件,即Logs 组件,它显示 页面按钮上每个用户提交的详细信息

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

启动应用程序后,我输入体重和身高,然后单击calculate 按钮,数据成功绘制到图表中,其相应的详细信息记录在页面底部,这正是我想要,但问题是用户输入的数据仅在第一次输入时成功显示,当我第二次输入数据时,在后台正确完成计算并计算第二次输入的结果,但没有任何变化在屏幕上,我只能看到第一个条目的结果,然后一旦我开始为第三个条目输入数据,我会得到第一个的结果以及我之前输入的第二个条目显示在屏幕上,只有结果当我开始为下一个条目输入数据时显示,而不是在单击 calculate 按钮后立即显示。 这是我想要实现的示例:

如果你想在你的机器上测试它,这里是一个 repo 的链接: https://github.com/Marouane328/Bmi-tracker

【问题讨论】:

  • 一些注意事项: (1) 不要将标记放入状态。 (2) 不要仅仅使用 refs 来设置按钮的属性;只需使用道具。 (3) BMI 结果应该是一个计算值,而不是状态的一部分,因为它不是用户可编辑的。 (4) 应该没有全局的dates/bmiResults/...变量;任何更改都不会被任何东西跟踪,也不会重新渲染。
  • @AKX 感谢您的注释,您说我不应该使用 refs 来设置按钮的属性,我能做些什么呢?我将 BMI 结果设置为状态的一部分,因为只有在计算结果时才会更新状态,因此重新渲染
  • &lt;button disabled={isNaN(parseFloat(height) &amp;&amp; parseFloat(weight))} /&gt;
  • 而且您不需要将 BMI 置于状态,因为身高和体重已经处于状态。如果身高和体重发生变化,组件会重新渲染,您可以根据这些计算组件内的 BMI。
  • @AKX 谢谢,我没注意,谢谢!

标签: javascript reactjs


【解决方案1】:

在 Graph 中,是否可以考虑将 const data 和 const options 转换为状态并使用 useEffect 将 props 值分配给状态,从而使 props 具有对 useEffect 的依赖关系。

试试这个,它在你的代码库中工作,代码和框(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]
)

【讨论】:

  • 这应该使用useMemo。
  • @Vishwa 为什么把props放在Graph组件的依赖数组上?
  • 所以只要 props 发生变化,useEffect 就会被执行,并且最新的值会被附加到你的状态钩子上。该解决方案对您有用吗?
猜你喜欢
  • 2019-09-16
  • 2019-06-17
  • 2019-02-18
  • 1970-01-01
  • 2021-05-19
  • 2022-01-26
  • 2019-06-29
  • 2020-02-18
  • 2019-07-30
相关资源
最近更新 更多