You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现点赞/点踩操作时实时切换按钮状态?

问题解决:点赞/点踩按钮状态不切换的修复

核心问题分析

  • 你用了全局的userClickedLike布尔值状态,所有照片共用同一个状态,导致点击某张照片的点赞后,所有按钮都会切换状态,完全不符合需求。
  • 直接修改gridData和userLikedPhotos的原对象(比如userLikedPhotos[likedPhotoUserId] = true、gridData.find(...).likes++),违背了React状态更新的不可变性原则,组件无法感知到状态变化,自然不会重新渲染更新按钮。
  • 按钮显示的条件判断!photos.is_liked && !userClickedLike逻辑混乱,两个状态来源冲突,导致判断失效。

修复方案

  1. 移除全局的userClickedLike/userClickedDisliked状态,改用userLikedPhotos对象单独跟踪每张照片的点赞状态,键用photo_id(比UserID更准确,一个用户可能上传多张照片)。
  2. 严格遵循状态不可变性:更新gridData和userLikedPhotos时,必须创建新对象/数组,不能直接修改原状态。
  3. 简化按钮显示条件:直接通过userLikedPhotos[photos.photo_id]判断当前照片是否被点赞,同时结合接口返回的photos.is_liked做初始化。

修改后的完整代码

const Grid = () => {
  const authToken = localStorage.getItem("token");

  const [gridData, setGridData] = useState([]);
  // 用photo_id作为键,跟踪每张照片的点赞状态
  const [userLikedPhotos, setUserLikedPhotos] = useState({});

  useEffect(() => {
    const headers = {
      Accept: "application/json",
      Authorization: `Bearer ${authToken}`,
    };

    axios
      .get("http://127.0.0.1:8000/api/get-user-uploads-data", { headers })
      .then((resp) => {
        console.log(resp.data);
        setGridData(resp.data);
        // 初始化点赞状态:根据接口返回的is_liked设置
        const initialLikes = {};
        resp.data.forEach(photo => {
          initialLikes[photo.photo_id] = photo.is_liked;
        });
        setUserLikedPhotos(initialLikes);
      })
      .catch((err) => {
        console.log(err);
      });
  }, []);

  const handleLike = (likedPhotoUserId, userName, likedPhotoId) => {
    const url = "http://127.0.0.1:8000/api/like";
    const headers = {
      Accept: "application/json",
      Authorization: `Bearer ${authToken}`,
    };
    const data = {
      UserID: likedPhotoUserId,
      userName: userName,
      likedPhotoId: likedPhotoId,
    };

    // 更新本地状态,优化用户体验(无需等接口返回)
    setUserLikedPhotos(prev => ({
      ...prev,
      [likedPhotoId]: true
    }));
    // 更新点赞数,创建新数组避免修改原状态
    setGridData(prev => prev.map(photo => {
      if (photo.photo_id === likedPhotoId) {
        return { ...photo, likes: photo.likes + 1 };
      }
      return photo;
    }));

    axios
      .post(url, data, { headers })
      .then((resp) => {
        // 如果接口返回新的状态,这里可以再同步一次,防止本地和接口不一致
        // setUserLikedPhotos(...)
      })
      .catch((err) => {
        console.log(err);
        // 接口失败时回滚状态
        setUserLikedPhotos(prev => ({
          ...prev,
          [likedPhotoId]: false
        }));
        setGridData(prev => prev.map(photo => {
          if (photo.photo_id === likedPhotoId) {
            return { ...photo, likes: photo.likes - 1 };
          }
          return photo;
        }));
      });
  };

  const handleDislike = (likedPhotoUserId, userName, likedPhotoId) => {
    const url = "http://127.0.0.1:8000/api/dislike";
    const headers = {
      Accept: "application/json",
      Authorization: `Bearer ${authToken}`,
    };
    const data = {
      UserID: likedPhotoUserId,
      userName: userName,
      dislikedPhotoId: likedPhotoId,
    };

    // 更新本地状态
    setUserLikedPhotos(prev => ({
      ...prev,
      [likedPhotoId]: false
    }));
    // 更新点赞数
    setGridData(prev => prev.map(photo => {
      if (photo.photo_id === likedPhotoId) {
        return { ...photo, likes: photo.likes - 1 };
      }
      return photo;
    }));

    axios
      .post(url, data, { headers })
      .then((resp) => {
        // 接口返回后同步状态(可选)
      })
      .catch((err) => {
        console.log(err);
        // 接口失败回滚状态
        setUserLikedPhotos(prev => ({
          ...prev,
          [likedPhotoId]: true
        }));
        setGridData(prev => prev.map(photo => {
          if (photo.photo_id === likedPhotoId) {
            return { ...photo, likes: photo.likes + 1 };
          }
          return photo;
        }));
      });
  };

  return (
    <>
      <div className="container">
        <div className="img-container">
          {gridData.map((photo, index) => {
            // 用photo_id判断当前照片是否被点赞
            const isLiked = userLikedPhotos[photo.photo_id];
            return (
              <div key={photo.photo_id} className="userDetails">
                <span className="likesAmt">❤️ {photo.likes}</span>
                <br />
                {!isLiked ? (
                  <Button
                    variant="success"
                    onClick={() =>
                      handleLike(
                        photo.UserID,
                        photo.name,
                        photo.photo_id
                      )
                    }
                  >
                    Like
                  </Button>
                ) : (
                  <Button
                    variant="danger"
                    onClick={() =>
                      handleDislike(
                        photo.UserID,
                        photo.name,
                        photo.photo_id
                      )
                    }
                  >
                    Dislike
                  </Button>
                )}
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
};

export default Grid;

额外优化点

  • 给map循环的元素加上唯一key(用photo.photo_id),符合React列表渲染规范。
  • 增加了接口请求失败时的状态回滚,避免本地状态和服务器状态不一致。
  • 初始化时根据接口返回的is_liked设置本地点赞状态,确保页面加载时显示正确的按钮状态。

内容的提问来源于stack exchange,提问作者sp92

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 18:55:34