反应钩子状态退一步而不更新

时间:2021-04-12 08:05:36

标签: reactjs react-hooks

我正在尝试创建一个平均计算器,我在下拉菜单和文本字段上的 OnChange 事件更新了挂钩值,但是当我尝试获取最终值时,但是要计算,我现在需要按 2 次按钮,否则它会不考虑钩子的最新状态,请让我知道我在这里缺少什么,我尝试使用 async await 但没有任何好处。以下是代码

    import React from 'react';
    import { makeStyles } from '@material-ui/core/styles';
    import InputLabel from '@material-ui/core/InputLabel';
    import MenuItem from '@material-ui/core/MenuItem';
    import FormHelperText from '@material-ui/core/FormHelperText';
    import FormControl from '@material-ui/core/FormControl';
    import Select from '@material-ui/core/Select';
    import TextField from '@material-ui/core/TextField';
    import Button from '@material-ui/core/Button';
    
    
    const useStyles = makeStyles((theme) => ({
        root: {
            '& .MuiTextField-root': {
                margin: theme.spacing(1),
                width: '25ch',
            },
        },
        formControl: {
            margin: theme.spacing(1),
            minWidth: 120,
        },
        selectEmpty: {
            marginTop: theme.spacing(2),
        },
    }));
    
    export default function AverageCalculator() {
        const classes = useStyles();
    
        const [price1, setPrice1] = React.useState(7500);
        const [price2, setPrice2] = React.useState(7300);
        const [price3, setPrice3] = React.useState(7200);
        const [price4, setPrice4] = React.useState(0);
    
        const [percentage1, setPercentage1] = React.useState(1);
        const [percentage2, setPercentage2] = React.useState(2);
        const [percentage3, setPercentage3] = React.useState(1);
        const [percentage4, setPercentage4] = React.useState(1);
    
        const [portfolioUsed, setportfolioUsed] = React.useState(0);
        const [avgPrice, setAvgPrice] = React.useState(0);
    
        const [update, setUpdate] = React.useState(true);
    
        const handleApply = (event) => {
            setUpdate(!update)
            console.log("PPPPP", update)
        }
    
        React.useEffect(async() => {
            await setportfolioUsed((price1 > 0 ? percentage1 : 0) + (price2 > 0 ? percentage2 : 0) + (price3 > 0 ? percentage3 : 0) + (price4 > 0 ? percentage4 : 0))
            await setAvgPrice((price1 * percentage1 + price2 * percentage2 + price3 * percentage3 + price4 * percentage4) / portfolioUsed)
        }, [update])
    
        return (
            <div>
                <form className={classes.root} noValidate autoComplete="off">
                    <TextField id="price1" value={price1} label="Primary Buying" onChange={(e) => setPrice1(e.target.value)} />
                    <TextField
                        id="perc1"
                        select
                        label="Portfolio"
                        value={percentage1}
                        onChange={(e) => setPercentage1(e.target.value)}
                    >
                        <MenuItem value={0}>0%</MenuItem>
                        <MenuItem value={1}>25%</MenuItem>
                        <MenuItem value={2}>50%</MenuItem>
                        <MenuItem value={3}>75%</MenuItem>
                        <MenuItem value={4}>100%</MenuItem>
                    </TextField>
                </form>
                <form className={classes.root} noValidate autoComplete="off">
                    <TextField id="price2" value={price2} label="Backup 1" onChange={(e) => setPrice2(e.target.value)} />
                    <TextField
                        id="perc2"
                        select
                        label="Portfolio"
                        value={percentage2}
                        onChange={(e) => setPercentage2(e.target.value)}
                    >
                        <MenuItem value={0}>0%</MenuItem>
                        <MenuItem value={1}>25%</MenuItem>
                        <MenuItem value={2}>50%</MenuItem>
                        <MenuItem value={3}>75%</MenuItem>
                        <MenuItem value={4}>100%</MenuItem>
                    </TextField>
                </form>
                <form className={classes.root} noValidate autoComplete="off">
                    <TextField id="price3" value={price3} label="Backup 2" onChange={(e) => setPrice3(e.target.value)} />
                    <TextField
                        id="perc3"
                        select
                        label="Portfolio"
                        value={percentage3}
                        onChange={(e) => setPercentage3(e.target.value)}
                    >
                        <MenuItem value={0}>0%</MenuItem>
                        <MenuItem value={1}>25%</MenuItem>
                        <MenuItem value={2}>50%</MenuItem>
                        <MenuItem value={3}>75%</MenuItem>
                        <MenuItem value={4}>100%</MenuItem>
                    </TextField>
                </form>
                <form className={classes.root} noValidate autoComplete="off">
                    <TextField id="price4" value={price4} label="Backup 3" onChange={(e) => setPrice4(e.target.value)} />
                    <TextField
                        id="perc4"
                        select
                        label="Portfolio"
                        value={percentage4}
                        onChange={(e) => setPercentage4(e.target.value)}
                    >
                        <MenuItem value={0}>0%</MenuItem>
                        <MenuItem value={1}>25%</MenuItem>
                        <MenuItem value={2}>50%</MenuItem>
                        <MenuItem value={3}>75%</MenuItem>
                        <MenuItem value={4}>100%</MenuItem>
                    </TextField>
                </form>
                <p>{portfolioUsed > 4 ? "Your portfolio percentage selection is wrong" : "Average Price: " + avgPrice}</p>
    
                <Button variant="contained" color="primary" onClick={()=>setUpdate(prevState=>!prevState)}>
                    Apply
                </Button>
    
            </div>
        );
    }

2 个答案:

答案 0 :(得分:1)

检查您的 useEffect 函数。您正在传递一个异步函数,但由于 useEffect 的工作方式,您遇到了竞争条件。在 useEffect 函数中进行异步操作的正确方法是这样的

React.useEffect(()=> {
          const asyncAction = async() => {
            await setportfolioUsed((price1 > 0 ? percentage1 : 0) + (price2 > 0 ? percentage2 : 0) + (price3 > 0 ? percentage3 : 0) + (price4 > 0 ? percentage4 : 0))
            await setAvgPrice((price1 * percentage1 + price2 * percentage2 + price3 * percentage3 + price4 * percentage4) / portfolioUsed)
        }
      
      
        asyncAction();
      }, [update])

如果你使用 react linter,你会看到这样的警告:

'await' 对这个表达式的类型没有影响。ts(80007) Effect 回调是同步的,以防止竞争条件。把异步函数放在里面:

此处提供了重现问题的链接: bad use of react useEffect

答案 1 :(得分:0)

这是因为 useState 本质上是异步的,但您不能简单地 await 获得 sync 行为。同样正如@rubendmatos1985 所提到的,您不应将 asyn 函数传递给 useEffect

就修复而言,您可以像这样简单地解决问题

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import InputLabel from "@material-ui/core/InputLabel";
import MenuItem from "@material-ui/core/MenuItem";
import FormHelperText from "@material-ui/core/FormHelperText";
import FormControl from "@material-ui/core/FormControl";
import Select from "@material-ui/core/Select";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";

const useStyles = makeStyles((theme) => ({
  root: {
    "& .MuiTextField-root": {
      margin: theme.spacing(1),
      width: "25ch"
    }
  },
  formControl: {
    margin: theme.spacing(1),
    minWidth: 120
  },
  selectEmpty: {
    marginTop: theme.spacing(2)
  }
}));

export default function AverageCalculator() {
  const classes = useStyles();

  const [price1, setPrice1] = React.useState(7500);
  const [price2, setPrice2] = React.useState(7300);
  const [price3, setPrice3] = React.useState(7200);
  const [price4, setPrice4] = React.useState(0);

  const [percentage1, setPercentage1] = React.useState(1);
  const [percentage2, setPercentage2] = React.useState(2);
  const [percentage3, setPercentage3] = React.useState(1);
  const [percentage4, setPercentage4] = React.useState(1);

  const [portfolioUsed, setportfolioUsed] = React.useState(0);
  const [avgPrice, setAvgPrice] = React.useState(0);

  const handleApply = () => {
    const newPortfolioUsed =
      (price1 > 0 ? percentage1 : 0) +
      (price2 > 0 ? percentage2 : 0) +
      (price3 > 0 ? percentage3 : 0) +
      (price4 > 0 ? percentage4 : 0);

    setportfolioUsed(newPortfolioUsed);
    setAvgPrice(
      (price1 * percentage1 +
        price2 * percentage2 +
        price3 * percentage3 +
        price4 * percentage4) /
        newPortfolioUsed
    );
  };

  return (
    <div>
      <form className={classes.root} noValidate autoComplete="off">
        <TextField
          id="price1"
          value={price1}
          label="Primary Buying"
          onChange={(e) => setPrice1(e.target.value)}
        />
        <TextField
          id="perc1"
          select
          label="Portfolio"
          value={percentage1}
          onChange={(e) => setPercentage1(e.target.value)}
        >
          <MenuItem value={0}>0%</MenuItem>
          <MenuItem value={1}>25%</MenuItem>
          <MenuItem value={2}>50%</MenuItem>
          <MenuItem value={3}>75%</MenuItem>
          <MenuItem value={4}>100%</MenuItem>
        </TextField>
      </form>
      <form className={classes.root} noValidate autoComplete="off">
        <TextField
          id="price2"
          value={price2}
          label="Backup 1"
          onChange={(e) => setPrice2(e.target.value)}
        />
        <TextField
          id="perc2"
          select
          label="Portfolio"
          value={percentage2}
          onChange={(e) => setPercentage2(e.target.value)}
        >
          <MenuItem value={0}>0%</MenuItem>
          <MenuItem value={1}>25%</MenuItem>
          <MenuItem value={2}>50%</MenuItem>
          <MenuItem value={3}>75%</MenuItem>
          <MenuItem value={4}>100%</MenuItem>
        </TextField>
      </form>
      <form className={classes.root} noValidate autoComplete="off">
        <TextField
          id="price3"
          value={price3}
          label="Backup 2"
          onChange={(e) => setPrice3(e.target.value)}
        />
        <TextField
          id="perc3"
          select
          label="Portfolio"
          value={percentage3}
          onChange={(e) => setPercentage3(e.target.value)}
        >
          <MenuItem value={0}>0%</MenuItem>
          <MenuItem value={1}>25%</MenuItem>
          <MenuItem value={2}>50%</MenuItem>
          <MenuItem value={3}>75%</MenuItem>
          <MenuItem value={4}>100%</MenuItem>
        </TextField>
      </form>
      <form className={classes.root} noValidate autoComplete="off">
        <TextField
          id="price4"
          value={price4}
          label="Backup 3"
          onChange={(e) => setPrice4(e.target.value)}
        />
        <TextField
          id="perc4"
          select
          label="Portfolio"
          value={percentage4}
          onChange={(e) => setPercentage4(e.target.value)}
        >
          <MenuItem value={0}>0%</MenuItem>
          <MenuItem value={1}>25%</MenuItem>
          <MenuItem value={2}>50%</MenuItem>
          <MenuItem value={3}>75%</MenuItem>
          <MenuItem value={4}>100%</MenuItem>
        </TextField>
      </form>
      <p>
        {portfolioUsed > 4
          ? "Your portfolio percentage selection is wrong"
          : "Average Price: " + avgPrice}
      </p>

      <Button variant="contained" color="primary" onClick={handleApply}>
        Apply
      </Button>
    </div>
  );
}

另外,我建议您尽量减少您拥有的状态变量的数量。你可以这样做

const [prices, setPrices] = useState({});
const [percentages, setPercentages] = useState({});

和像这样的处理程序

const handleChangePrice = (e) => {
    const { name, value } = e.target;
    setPrices({
      ...prices,
      [name]: value
    });
  };
  const handleChangePercentage = (e) => {
    const { name, value } = e.target;
    setPercentages({
      ...percentages,
      [name]: value
    });
  };

和你的Input一样

<TextField
 id="price1"
 value={prices.price1}
 label="Primary Buying"
 onChange={handleChangePrice}
/>