Home > other >  useEffect re-renders twice with socket.io
useEffect re-renders twice with socket.io

Time:10-12

I'm still new to react, but I'm currently facing a issue regarding to updating the state; I'm making use of socket.io and change streams to check for any updates in the database (post document). Every time a new post is created, the useEffect seems to be logging twice, one is the change that has been made in the socket.io and the iterating array which I mainly want. So some how it's causing the useEffect to render twice. How would I go about on fixing this? Any help would be really appreciated!

Issue (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);
        }
    })

}); 

Client side

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('');
}

I have done a bit of reading regarding re-rendering would normally occur due to React.StrictMode being in use, but I guess that wouldn't be the case as I have removed it. Below I'll provide the full client code, don't mind the length.

Full Client Code:

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);

CodePudding user response:

Using socket.on and socket.off every single time a new post is sent would be unfortunate. Luckily, you can use setPosts without adding it to the dependencies list (I assume you want to setPosts and not just 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);
}, []);
  • Related