【问题标题】:useEffect re-renders twice with socket.iouseEffect 使用 socket.io 重新渲染两次
【发布时间】:2021-10-12 04:47:33
【问题描述】:

我还是新手,但我目前面临更新状态的问题;我正在使用 socket.io 并更改流来检查数据库中的任何更新(发布文档)。每次创建新帖子时,useEffect 似乎都会记录两次,其中一个是在 socket.io 和我主要想要的迭代数组中所做的更改。所以一些它是如何导致 useEffect 渲染两次的。我将如何解决这个问题?任何帮助将不胜感激!

问题(console.log)

console.log() output

Server/Change Stream/Socket Emit()

const db = mongoose.connection;
db.once('open', () => {
    console.log(chalk.blueBright("Setting change streams"));
    db.collection('posts').watch()
    .on('change', change => {
        if(change.operationType === 'insert'){
            console.log(chalk.yellowBright('INSERTED'))
            io.emit('posts', change.fullDocument);
        }
    });
    db.collection('posts').watch({ fullDocument: 'updateLookup' })
    .on('change', change => {
        if(change.operationType === 'update'){
            console.log(chalk.yellowBright('UPDATED'));
            io.emit('posts', change.fullDocument);
        }
    })

}); 

客户端

const [posts, setPosts] = useState([]);

useEffect(() => {
    axios.get("/api/post/posts",config).then((response) => {
        setPosts(response.data);
    });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
    socket.on('posts', (item) => {
       console.log([...posts, item]);
    })
},[posts])

const postHandler = (e) => {
    e.preventDefault();
    dispatch(newPost(uname, upic, messages, media));
    setMessages('');
}

我已经阅读了一些关于由于 React.StrictMode 正在使用而通常会发生重新渲染的信息,但我想这不会是因为我已经删除了它。下面我将提供完整的客户端代码,不要介意长度。

完整的客户代码:

import React, { useState, useEffect, useReducer, useRef } from 'react';
import axios from 'axios';
import Cookies from 'universal-cookie';
import '../../styles/private/dashboard.css';
import DashboardHeader from '../../components/private/templates/header';
import DashboardSidebar from '../../components/private/templates/sidebar';
import ImageSearchIcon from '@material-ui/icons/ImageSearch';
import VideoLibraryIcon from '@material-ui/icons/VideoLibrary';
import FavoriteIcon from '@material-ui/icons/Favorite';
import SendIcon from '@material-ui/icons/Send';
import { Avatar } from '@material-ui/core';
import { useSelector, useDispatch } from 'react-redux';
import { newPost } from '../../redux/actions/posts/new-post';
import { likePost } from '../../redux/actions/posts/like-post';
import { getPosts } from '../../redux/actions/posts/get-posts';
import { unlikePost } from '../../redux/actions/posts/unlike-post';
import { getPostLikes } from '../../redux/actions/posts/get-likes';
import { likePostComment } from '../../redux/actions/posts/like-comment';
import { unlikePostComment } from '../../redux/actions/posts/unlike-comment';
import { newPostComment } from '../../redux/actions/posts/new-post-comment';
import ChatBubbleOutlineIcon from '@material-ui/icons/ChatBubbleOutline';
import LoopIcon from '@material-ui/icons/Loop';
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
import Pusher from 'pusher-js';
import FlipMove from 'react-flip-move';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import io from 'socket.io-client';

const socket = io.connect('http://localhost:5000');

function Dashboard({ history, getPost, getLike, getAllPosts, getAllLikes, likePosts, unlikePosts}) {
    const cookies = new Cookies();
    const [toggle, setToggle] = useState(false);
    const [messages, setMessages] = useState('');
    const [media, setMedia] = useState(null);
    const [posts, setPosts] = useState([]);
    const [error, setError] = useState('');
    const [comment, setComment] = useState();
    const userLogin = useSelector((state) => state.userLogin);
    const { user } = userLogin;
    const [uname, setUname] =useState(user.name);
    const [upic, setUpic] = useState(user.pic);
    const dispatch = useDispatch();

    const config = {
        headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${user.token}`, 
        },
    };
    
    useEffect(() => {
        if(!cookies.get('authToken')){
            history.push('/login');
        }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    },[history]);

    useEffect(() => {
        axios.get("/api/post/posts",config).then((response) => {
            setPosts(response.data);
        });
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    useEffect(() => {
        socket.on('posts', (item) => {
            console.log([...posts, item]);
        })
    },[posts])

    const postHandler = (e) => {
        e.preventDefault();
        dispatch(newPost(uname, upic, messages, media));
        setMessages('');
    }

    const LikePost = (postId) => {
        likePosts(postId, user._id, user.name, user.pic);
    }

    const UnlikePost = (postId) => {
        unlikePosts(postId);
    }

    const submitComment = (postId) => {
        dispatch(newPostComment(postId, uname, upic, comment));
        setComment('');
    }

    const LikeCommentPost = (postId, commentId) => {
        dispatch(likePostComment(postId, commentId, user._id, user.name, user.pic));
    }

    const UnlikeCommentPost = (postId, commentId) => {
        dispatch(unlikePostComment(postId, commentId));
    }

    return error ? (
        <span>{error}</span>
    ):(
        <div className="dashboard">
            <DashboardHeader/>
            <div className="dashboard__container">
                <div className="dashboard__sidebar">
                    <DashboardSidebar/>
                </div>
                <div className="dashboard__content">
                    <div className="dashboard__contentLeft">
                        <div className="dashboard__messenger">
                            <div className="dashboard__messengerTop">
                                <Avatar src={user.pic} className="dashboard__messengerAvatar"/>
                                <input type="text" placeholder={`What's on your mind, ${user.name}`} value={messages} onChange={(e) => setMessages(e.target.value)}/>
                                <SendIcon className="dashboard__messengerPostButton" onClick={postHandler}/>
                            </div>
                            <div className="dashboard__messengerBottom">
                                <ImageSearchIcon className="dashboard__messengerImageIcon" value={media} onChange={(e) => setMedia((e) => e.target.value)}/>
                                <VideoLibraryIcon className="dashboard__messengerVideoIcon"/>
                            </div>
                        </div>

                        <div className="dashboard__postsContainer">
                            <FlipMove>
                                {posts.map((post,i) => (
                                    <div className="dashboard__post" key={i}>
                                        <MoreHorizIcon className="dashboard__postOptions"/>
                                        <div className="dashboard__postTop">
                                            <Avatar className="dashboard__postUserPic" src={post.upic}/>
                                            <h3>{post.uname}</h3>
                                        </div>
                                        <div className="dashboard__postBottom">
                                            <p>{post.message}</p>
                                        {media === null ? '':(
                                            <div className="dashboard__postMedia">
                                                {media}
                                            </div>
                                        )}
                                        </div>
                                        <div className="dashboard__postActions">
                                            {toggle ? (
                                                <ChatBubbleOutlineIcon onClick={() => setToggle(!toggle)} className="dashboard__actionComment"/>
                                            ): (
                                                <ChatBubbleOutlineIcon onClick={() => setToggle(!toggle)} className="dashboard__actionComment"/>
                                            )}
                                            <label id='totalLikes' className="dashboard__comments" style={{color: 'forestgreen'}}>
                                                    {post.commentCount}
                                            </label>

                                            {post.likes.find(like => like.uid === user._id) ? (
                                                <FavoriteIcon onClick={() => UnlikePost(post._id)} className="dashboard__actionUnlike"/>
                                            ):(
                                                <FavoriteBorderIcon onClick={() => LikePost(post._id)} className="dashboard__actionLike"/>
                                            )}              
                                                <label id='totalLikes' className="dashboard__likes" style={{color: 'forestgreen'}}>
                                                    {post.likeCount}
                                                </label>                                    
                                        </div>
                                        <div className={toggle ? "dashboard__commentContent toggle" : "dashboard__commentContent"}>
                                                <div className="dashboard__postComments">
                                                    {post.comments.map((comment) => (
                                                        <div key={comment.toString()} className="dashboard__postComment">
                                                            <div className="dashboard__postCommentTop">
                                                                <Avatar src={comment.upic}/>
                                                                <h4>{comment.uname}</h4>
                                                            </div>
                                                            <p>{comment.message}</p>
                                                            <div className="dashboard__postCommentActions">
                                                            {comment.likes.find(like => like.uid === user._id) ? (
                                                                <FavoriteIcon onClick={() => UnlikeCommentPost(post._id, comment._id)} className="dashboard__actionUnlike"/>
                                                            ):(
                                                                <FavoriteBorderIcon onClick={() => LikeCommentPost(post._id, comment._id)} className="dashboard__actionLike"/>
                                                            )}              
                                                                <label id='totalLikes' className="dashboard__likes" style={{color: 'forestgreen'}}>
                                                                    {comment.likeCount}
                                                                </label>   
                                                            </div>
                                                        </div>
                                                    ))}        
                                                </div>

                                            <div className="dashboard__commentInput">
                                                <input type="text" placeholder="Comment post" value={comment} onChange={(e) => setComment(e.target.value)}/>
                                                <button onClick={() => submitComment(post._id)}>Send</button>
                                            </div>
                                        </div>
                                    </div>
                                ))}
                            </FlipMove>
                        </div>
                    </div>
                    <div className="dashboardContentRight">
                    </div>
                </div>
            </div>
        </div>
    )
}

Dashboard.propTypes = {
    getLike: PropTypes.arrayOf(PropTypes.string),
    getPost: PropTypes.arrayOf(PropTypes.string),
    likePost: PropTypes.arrayOf(PropTypes.string),
    unlikePost: PropTypes.arrayOf(PropTypes.string),
}

function mapStateToProps(state) {
    return {
        getPost: getPosts(state),
        getLike: getPostLikes(state),
        likePosts: likePost(state),
        unlikePosts: unlikePost(state),
    }
}

function mapDispatchToProps(dispatch) {
    return {
        getAllPosts: (posts) => dispatch(getPosts(posts)),
        getAllLikes: (likes) => dispatch(getPostLikes(likes)),
        likePosts: (like) => dispatch(likePost(like)),
        unlikePosts: (like) => dispatch(unlikePost(like)),
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);

【问题讨论】:

  • 您正在为 posts 创建一个新的侦听器,但从未删除它们。更多细节在这里:stackoverflow.com/a/54830805/4384238
  • 您是否碰巧将您的应用程序渲染为React.StrictMode 组件?它有意双重调用某些函数,以帮助您检测无意的副作用。每次效果挂钩回调运行时,您都会添加另一个侦听器。
  • @DemiPixel - 我认为这应该是我在使用套接字连接时使用的东西,所以如果我是正确的,因为我正在使用 socket.on 里面useEffect,在 useEffect 发生后,连接真的永远不会关闭吗?从而迫使渲染发生两次?如果我弄错了,请不要介意纠正我,因为我还是新手。
  • useEffect 在第一次渲染时被调用——然后在 posts 更新时再次调用(在您的 API 获取之后)。所以现在它已经完成了两次 socket.on( 并且每次有 post 事件时你都会 console.log 两次。
  • @DemiPixel - 所以我猜你提供的链接(我现在正在阅读),宁愿建议处理与套接字有关的那些连接,以防止它在控制台中记录两次?如果要问的太多,您不介意为您提供的内容提供解决方案,只是为了检查两者之间的相似之处,只是为了获得两个示例以供理解,如果您不能,我会尊重并欣赏帮助!

标签: reactjs use-effect


【解决方案1】:

每次发送新帖子时都使用socket.onsocket.off 会很不幸。幸运的是,您可以使用 setPosts 而不将其添加到依赖项列表中(我假设您想要 setPosts 而不仅仅是 console.log):

useEffect(() => {
  const handler = (item) => {
    setPosts((oldPosts) => [...oldPosts, item]);
  };
  socket.on('posts', handler);
  // Otherwise you'll start getting errors when the component is unloaded
  return () => socket.off('posts', handler);
}, []);

【讨论】:

  • 工作得很好!!我一直在坐的一个小问题有点像在同一条船上,当我喜欢帖子时,状态似乎首先反映旧的(不喜欢的帖子)以及新的状态(喜欢的帖子),我该怎么走对这个?我一直在寻找其他建议,但我仍然在努力解决它,因为 setPosts((oldposts) => [...oldPosts, item]);已经使数组 Iterable 了?我想在呈现重复之前它需要以某种方式更新,或者应该在添加之前删除该帖子的旧状态?
  • 如果posts 事件返回所有帖子,只需执行setPosts(item)。如果它同时返回新帖子和更新帖子,则您需要做一些更有趣的事情,例如setPosts((oldPosts) =&gt; [...oldPosts.filter(p =&gt; p.id !== item.id), item]),假设相同的帖子具有相同的id 属性。过滤器将从现有的帖子数组中删除更新的帖子(如果它在那里)。
猜你喜欢
  • 2020-04-23
  • 2022-11-26
  • 1970-01-01
  • 1970-01-01
  • 2022-11-17
  • 1970-01-01
  • 1970-01-01
  • 2020-08-16
  • 1970-01-01
相关资源
最近更新 更多