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)
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);
}, []);