首页 > 解决方案 > React useState 多次触发

问题描述

我有一个like 按钮,一旦点击它就会将默认值setLikedNumbers(likedNumbers + 1);增加一。当再次按下按钮时,它会减一使用setLikedNumbers(likedNumbers - 1);这工作正常,直到每秒按下按钮多次,这会产生一些奇怪的值。React 严格模式标签被移除。

问题视频:https : //vimeo.com/593482477 一开始我慢慢地点击按钮,然后我每秒执行多次,然后 axios 赶上请求。

即使按钮在 axios 返回错误代码并返回当前值 - 1 之前多次递增,这不应该意味着保留原始值,因为增加的次数等于减少的次数吗?

我怀疑问题所在的代码(我删除了一些不需要的行):

组件

 <ToggleIcon
                    on={liked}
                    onIcon={
                      <FavoriteOutlinedIcon onClick={() => unlike(props.idData)} />
                    }
                    offIcon={
                      <FavoriteBorderIcon onClick={() => like(props.idData)} />
                    }
                  />

javascript

 const like = async (id) => {
        await setLiked(true);
        await setLikedNumbers(likedNumbers + 1);
        axios
          .request({
            method: "POST",
            url: `http://localhost:5000/like`,
            headers: {
              jwt: localStorage.getItem("jwt"),
            },
            data: {
              place_id: id,
            },
          })
          .catch(async (err) => {
            await setLiked(false);
            await setLikedNumbers(likedNumbers - 1);
          });
      };
      const unlike = async (id) => {
        await setLiked(false);
        await setLikedNumbers(likedNumbers - 1);
        axios
          .request({
            method: "POST",
            url: `http://localhost:5000/unlike`,
            headers: {
              jwt: localStorage.getItem("jwt"),
            },
            data: {
              place_id: id,
            },
          })
          .catch(async (err) => {
            await setLiked(true);
            await setLikedNumbers(likedNumbers + 1);
      };
    

整个代码

import React from "react";
import "react-responsive-carousel/lib/styles/carousel.min.css"; // requires a loader
import Box from "@material-ui/core/Box";
import Card from "@material-ui/core/Card";
import CardActionArea from "@material-ui/core/CardActionArea";
import CardActions from "@material-ui/core/CardActions";
import CardContent from "@material-ui/core/CardContent";
import CardMedia from "@material-ui/core/CardMedia";
import Typography from "@material-ui/core/Typography";
import FavoriteBorderIcon from "@material-ui/icons/FavoriteBorder";
import BookmarkBorderIcon from "@material-ui/icons/BookmarkBorder";
import Dialog from "@material-ui/core/Dialog";
import DialogTitle from "@material-ui/core/DialogTitle";
import DialogContent from "@material-ui/core/DialogContent";
import DialogActions from "@material-ui/core/DialogActions";
import { Carousel } from "react-responsive-carousel";
import ReportOutlinedIcon from "@material-ui/icons/ReportOutlined";
import ShareOutlinedIcon from "@material-ui/icons/ShareOutlined";
import FavoriteOutlinedIcon from "@material-ui/icons/FavoriteOutlined";
import BookmarkOutlinedIcon from "@material-ui/icons/BookmarkOutlined";
import ToggleIcon from "material-ui-toggle-icon";

const axios = require("axios");

const CardElement = (props) => {
  const [open, setOpen] = React.useState(false);
  const [liked, setLiked] = React.useState(props.liked);
  const [saved, setSaved] = React.useState(props.saved);
  const [likedNumbers, setLikedNumbers] = React.useState(props.numbersLiked);
  const handleClickOpen = () => {
    setOpen(true);
  };
  const like = async (id) => {
    await setLiked(true);
    await setLikedNumbers(likedNumbers + 1);
    axios
      .request({
        method: "POST",
        url: `http://localhost:5000/like`,
        headers: {
          jwt: localStorage.getItem("jwt"),
        },
        data: {
          place_id: id,
        },
      })
      .catch(async (err) => {
        await setLiked(false);
        await setLikedNumbers(likedNumbers - 1);
      });
  };
  const unlike = async (id) => {
    await setLiked(false);
    await setLikedNumbers(likedNumbers - 1);
    axios
      .request({
        method: "POST",
        url: `http://localhost:5000/unlike`,
        headers: {
          jwt: localStorage.getItem("jwt"),
        },
        data: {
          place_id: id,
        },
      })
      .catch(async (err) => {
        await setLiked(true);
        await setLikedNumbers(likedNumbers + 1);
  };

  const save = (id) => {
    setSaved(true);
    axios
      .request({
        method: "POST",
        url: `http://localhost:5000/save`,
        headers: {
          jwt: localStorage.getItem("jwt"),
        },
        data: {
          place_id: id,
        },
      })
      .catch((err) => {
        setSaved(false);
       
      });
  };
  return (
    <div>
      <Card className="card">
        <CardActionArea
          onClick={() => {
            handleClickOpen();
          }}
        >
          {props.mainImg ? (
            <CardMedia
              className="mediaImgOverview"
              image={"http://localhost:5000/image/" + props.mainImg}
            />
          ) : (
            ""
          )}
          <CardContent>
            <Typography gutterBottom variant="h5" component="h2">
              {props.title}
            </Typography>
            <Typography variant="body2" color="textSecondary" component="p">
              {props.description.length > 45
                ? props.description.substring(0, 45) + "..."
                : props.description}
            </Typography>
          </CardContent>
        </CardActionArea>
        <CardActions className="ButtonHolder">
          <Box className="likesContainer">
            {props.likeButtonVisible ? (
              <ToggleIcon
                on={liked}
                onIcon={
                  <FavoriteOutlinedIcon onClick={() => unlike(props.idData)} />
                }
                offIcon={
                  <FavoriteBorderIcon onClick={() => like(props.idData)} />
                }
              />
            ) : (
              ""
            )}
            <Typography
              style={{ marginLeft: props.likeButtonVisible ? 0 : "0.2vmax" }}
            >
              {likedNumbers}
            </Typography>
          </Box>
          {props.likeButtonVisible ? (
            <ToggleIcon
              on={saved}
              onIcon={
                <BookmarkOutlinedIcon onClick={() => unsave(props.idData)} />
              }
              offIcon={
                <BookmarkBorderIcon onClick={() => save(props.idData)} />
              }
            />
          ) : (
            ""
          )}
        </CardActions>
      </Card>
      <Dialog
        maxWidth="md"
        onClose={handleClose}
        aria-labelledby="MoreInfo"
        open={open}
      >
        <DialogTitle id="MoreInfo" onClose={handleClose}>
          {props.title}
        </DialogTitle>
        <DialogContent dividers>
          {props.images[0].url ? (
            <Carousel infiniteLoop="true">
              {props.images.map((el) => {
                return (
                  <div key={Math.random()}>
                    <img alt="" src={"http://localhost:5000/image/" + el.url} />
                    <p className="legend">{el.caption}</p>
                  </div>
                );
              })}
            </Carousel>
          ) : (
            ""
          )}

          <Typography gutterBottom>
            <Typography>
              <b>Категория: </b>
              {category(props.category)}
              <b> Опасно: </b>
              {dangerous(props.dangerous)}
              <b> Цена: </b>
              {price(props.price)} <b> Достъпност: </b>
              {accessibility(props.accessibility)}
            </Typography>
            {props.description}
          </Typography>
        </DialogContent>
        <DialogActions className="btnCard">
          <Box className="likesContainer">
            {props.likeButtonVisible ? (
              <ToggleIcon
                on={liked}
                onIcon={
                  <FavoriteOutlinedIcon onClick={() => unlike(props.idData)} />
                }
                offIcon={
                  <FavoriteBorderIcon onClick={() => like(props.idData)} />
                }
              />
            ) : (
              ""
            )}
            <Typography
              style={{ marginLeft: props.likeButtonVisible ? 0 : "0.2vmax" }}
            >
              {likedNumbers == 0
                ? "Няма харесвания"
                : likedNumbers == 1
                ? "1 харесване"
                : likedNumbers + " харесвания"}
            </Typography>
          </Box>
          <Box>
            {props.likeButtonVisible ? (
              <ToggleIcon
                on={saved}
                onIcon={
                  <BookmarkOutlinedIcon onClick={() => unsave(props.idData)} />
                }
                offIcon={
                  <BookmarkBorderIcon onClick={() => save(props.idData)} />
                }
              />
            ) : (
              ""
            )}
            <ShareOutlinedIcon />
            {props.reportButtonVisible ? <ReportOutlinedIcon /> : ""}
          </Box>
        </DialogActions>
      </Dialog>
    </div>
  );
};
export default CardElement;

更新 1

经过几次实验后,我相当确定问题是按钮(动画)的缓慢变化导致触发其他功能,例如喜欢而不是不同,反之亦然。我正在尝试找到解决此问题的方法。

更新 2 通过创建一个包装函数来解决问题,该函数调用正确的类似/不类似函数,而不管存在哪个按钮。

标签: javascriptreactjsstate

解决方案


我建议查看RxJS lib并限制对 api 的请求数量(它将提高浏览器性能)。查看下面的简短片段:

// Create a subject which will store the latest value
const buttonClicked = new Subject<{itemId: string, isLiked: boolean}>();

// Debounce for 200 sec.
// debounceTime - Emits a notification from the source Observable only after a particular time span has passed without another source emission. (ref: https://rxjs.dev/api/operators/debounceTime)
const buttonClickedDebounced = buttonClicked.pipe(debounceTime(200));

// If no click was triggered due the time, execute the call to api with latest params. 
buttonClickedDebounced.subscribe(({itemId: string, isLiked: boolean}) =>
     {
        axios.request({...})
        // After you get a response it's nice to update the number or likes as well, as soon the other User could like/unlike the same card.
     }
);

// Register the click event
function like(itemId, isLiked) {
 setLiked(isLiked)
 buttonClicked.next({itemId, isLiked})
}

使用这种方法,您将仅向 api 发送 1 个请求,其中包含用户在多次单击后决定离开(喜欢或不喜欢)的最新状态。希望这将有助于改进您的应用程序!


推荐阅读