基于api调用的反应js中的按钮不改变状态

Posted

技术标签:

【中文标题】基于api调用的反应js中的按钮不改变状态【英文标题】:Button not changing state in react js based on api call 【发布时间】:2021-02-08 01:46:28 【问题描述】:

我有一个显示单个帖子的页面,我有一个赞按钮。如果帖子被点赞,当用户单击按钮时,它的状态会更改为不喜欢按钮,但如果帖子不被点赞,则会注册点赞并将 id 推送到数组中,但按钮状态没有得到更新,我必须重新加载页面才能看到该页面。谁能告诉我如何解决这个问题?

这是代码:

    const [liked, setLiked] = useState(false)

    const [data, setData] = useState([]);

    function likePosts(post, user) 
        post.likes.push( id: user );
        setData(post);
        axiosInstance.post('api/posts/' + post.slug + '/like/');
        window.location.reload()
    

    function unlikePosts(post, user) 
        console.log('unliked the post');
        data.likes = data.likes.filter(x => x.id !== user);
        setData(data);
        return (
            axiosInstance.delete('api/posts/' + post.slug + '/like/')
        )
    

对于按钮:

data.likes && data.likes.find(x => x.id === user) ?
        (<FavoriteRoundedIcon style= color: "red" 
            onClick=() => 
                unlikePosts(data, user)
                setLiked(() => liked === false)
               
        
        />)
        : (<FavoriteBorderRoundedIcon
               onClick=() => 
               likePosts(data, user)
               setLiked(() => liked === true)
               
        
        />)
    

谢谢,如果需要更多详细信息,请询问。

【问题讨论】:

你不应该直接改变状态;使用setData 嘿,你能解释一下吗? 我无法真正测试您的代码,但您分配给 data.likes 不应该这样做。另外,data 应该是什么?您的初始状态是一个数组,但您似乎将其用作对象。 是的,所以它应该是一个对象(你使用的是useState([]))。您可以使用setData(data =&gt; ...data, likes: data.likes.filter(...)) 之类的内容进行更改。请问,setLiked(() =&gt; liked === false) 应该做什么?为什么不setLiked(false) 你也直接在这里改变状态:post.likes.push(...)。我认为最好的方法是使用控制台日志记录或 React DevTools 来调试您的代码,因为如果我无法重现它就很难提供帮助。 【参考方案1】:

正如@iz_ 在 cmets 中指出的那样,您的主要问题是您直接改变状态而不是调用 setState 函数。

为了清楚起见,我将 data 重命名为 post,因为您说过这是一个表示一篇帖子数据的对象。

const [post, setPost] = useState(initialPost);

您不需要使用liked 作为状态,因为我们已经可以通过查看我们的用户是否在post.likes 数组中来从post 数据中访问此信息。这使我们能够拥有“单一事实来源”,并且我们只需要在一个地方进行更新。

const isLiked = post.likes.some((like) => like.id === user.id);

我对@9​​87654331@ 数组感到困惑。这似乎是一个对象数组,只是 id: number,在这种情况下,您应该只拥有一个喜欢该帖子的用户的 id 数组。但也许对象中还有其他属性(如用户名或时间戳)。

在为博客文章之类的复杂内容设计组件时,您希望分解出可以在应用的其他地方使用的小块。我们可以定义一个LikeButton 来表达我们的心意。这是一个不处理任何逻辑的“演示文稿”组件。它只需要知道isLiked 是否发帖以及onClick 做什么。

export const LikeButton = ( isLiked, onClick ) => 
  const Icon = isLiked ? FavoriteRoundedIcon: FavoriteBorderRoundedIcon;
  return (
    <Icon
      style= color: isLiked ? "red" : "gray" 
      onClick=onClick
    />
  );
;

我们很多关于喜欢和不喜欢的逻辑可能会分解成某种usePostLike 钩子,但我还没有完全优化这一点,因为我不知道你的 API 在做什么以及我们应该如何响应对我们得到的响应。

当用户单击“赞”按钮时,我们希望更改立即反映在 UI 中,因此我们调用 setPost 并在 likes 数组中添加或删除当前用户。我们必须用一个新对象来设置状态,所以我们复制所有不使用扩展运算符...post 更改的帖子属性,然后用编辑的版本覆盖likes 属性。 filter()concat() 都是安全数组函数,它们返回数组的新副本。

我们还需要调用 API 来发布更改。您在“like”和“unlike”场景中使用相同的 url,因此我们可以调用泛化函数 axios.request 并传递方法名称 'post' 或 @987654348,而不是调用 axios.postaxios.delete @ 作为 config 对象的参数。 [axios docs] 我们或许可以以类似的方式组合我们的两个 setPost 调用,并将 likePost()unlikePost() 更改为一个 toggleLikePost() 函数。但现在,这就是我所拥有的:

export const Post = ( initialPost, user ) => 
  const [post, setPost] = useState(initialPost);

  const isLiked = post.likes.some((like) => like.id === user.id);

  function likePost() 
    console.log("liked the post");
    // immediately update local state to reflect changes
    setPost(
      ...post,
      likes: post.likes.concat( id: user.id )
    );
    // push changes to API
    apiUpdateLike("post");
  

  function unlikePost() 
    console.log("unliked the post");
    // immediately update local state to reflect changes
    setPost(
      ...post,
      likes: post.likes.filter((like) => like.id !== user.id)
    );
    // push changes to API
    apiUpdateLike("delete");
  

  // generalize like and unlike actions by passing method name 'post' or 'delete'
  async function apiUpdateLike(method) 
    try 
      // send request to API
      await axiosInstance.request("api/posts/" + post.slug + "/like/",  method );
      // handle API response somehow, but not with window.location.reload()
     catch (e) 
      console.log(e);
    
  

  function onClickLike() 
    if (isLiked) 
      unlikePost();
     else 
      likePost();
    
  

  return (
    <div>
      <h2>post.title</h2>
      <div>post.likes.length Likes</div>
      <LikeButton onClick=onClickLike isLiked=isLiked />
    </div>
  );
;

CodeSandbox Link

【讨论】:

非常感谢您的回答!我会尝试这种方式:) 嘿@Linda,我试过这个,但我在这一行遇到错误,const isLiked = post.likes.some((like) =&gt; like.id === user.id);,这是错误TypeError: Cannot read property 'likes' of undefined。你能调查一下吗?正如你所说,likes 数组包含用户的 id 和用户名。 这是我的console.log(post) 看起来像id: 152 likes: [] post_date: "2020-10-25T09:21:14.531431+01:00" postimage_set: (2) […, …] display_pic: "http://127.0.0.1:8000/media/pimage/1/profile_pic_C68UK9J.png" slug: "just-checking-multiple-upload" title: "Just checking multiple upload!" user: "testuser12" 但是当我尝试console.log(post.likes) 时它会抛出这个错误TypeError: Cannot read property 'likes' of undefined。这是为什么?谢谢 如果我像const isLiked = post &amp;&amp; post.likes &amp;&amp; post.likes.some((like) =&gt; like.id === user.id); 那样更改函数,它不会显示任何错误,并且喜欢和不同的作品,但问题是当我重新加载页面时,即使用户按钮处于不同状态已经喜欢了这篇文章。 Re: TypeError,可能是我们在post 存在之前访问它太早了? post 将是该错误中的 undefined 对象。我假设帖子已经加载,这就是我使用initialPost 的原因。 Idk,但您可以使用可选链接 const isLiked = post?.likes.some((like) =&gt; like.id === user?.id);

以上是关于基于api调用的反应js中的按钮不改变状态的主要内容,如果未能解决你的问题,请参考以下文章

通过反应上下文 api 将子状态传递给父组件

当反应原生应用程序启动时,将初始状态从 API 调用传递给 createStore

有没有办法在 useEffect 的反应组件中设置 xstate 机器的状态?

如何访问在另一个 js 文件中的反应挂钩中完成的状态值

反应前端中的 CSRF 令牌不匹配

使用 Context API 在函数组件中反应未定义的状态属性