Reputation:
I have a post details component where on clicking the like button the redux state changes the redux state is like
posts ->postDetails
I'am changing the liked property and number of likes of postDetais object, On clicking the like button the liked property is set to true from false and vice versa and the number of likes is incremented.
However the state is changing but the componentDidUpdate method is not firing
PostDetails.js
import React, { Component } from "react";
import { connect } from "react-redux";
import {
getPostData,
likePost,
unlikePost
} from "../../store/actions/postsActions";
import { Icon, Tooltip } from "antd";
import { Link } from "react-router-dom";
export class PostDetails extends Component {
state = {
postData: this.props.postDetails
};
componentDidMount() {
this.props.getPostData(this.props.match.params.post_id);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(this.props.postDetails);
if (prevProps.postDetails !== this.props.postDetails) {
this.setState({
postData: this.props.postDetails
});
}
}
render() {
const { postData } = this.state;
const liked = postData.liked;
return (
<div className="postDetails">
{postData && (
<div className="postDetailsContainer">
<div className="postImage">
<img src={postData.imageUrl} alt={postData.caption} />
</div>
<div className="postContent">
<div className="postContent__header">
<Link
to={`/user/${postData.username}`}
className="postContent__headerContent"
>
<img
src={postData.profileUrl}
alt={postData.username}
className="postContent__profileImage"
/>
<p className="postContent__username">{postData.username}</p>
</Link>
</div>
<div className="postComments" />
<div className="postInfo">
<div className="postActions">
{liked ? (
<Tooltip title="Unlike post">
<Icon
type="heart"
className="likePost"
theme="filled"
style={{ color: "#d41c00" }}
onClick={() => this.props.unlikePost(postData.id)}
/>
</Tooltip>
) : (
<Tooltip title="Like post">
<Icon
type="heart"
className="likePost"
onClick={() => this.props.likePost(postData.id)}
/>
</Tooltip>
)}
<Tooltip title="Comment">
<Icon type="message" className="commentButton" />
</Tooltip>
</div>
<Tooltip title="Refresh comments">
<Icon type="reload" className="reloadComments" />
</Tooltip>
</div>
<div />
</div>
</div>
)}
</div>
);
}
}
const mapStateToProps = state => {
return {
postDetails: state.posts.postDetails
};
};
const mapDispatchToProps = dispatch => {
return {
getPostData: postId => dispatch(getPostData(postId)),
likePost: postId => dispatch(likePost(postId)),
unlikePost: postId => dispatch(unlikePost(postId))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(PostDetails);
postsReducer.js
const initialState = {
creatingPost: false,
feed: [],
createdPost: false,
feedUpdated: false,
postDetails: {}
};
const postsReducer = (state = initialState, action) => {
switch (action.type) {
case "CREATING_POST":
return {
...state,
creatingPost: true,
createdPost: false
};
case "ADD_POST":
return {
...state,
feed: state.feed.concat(action.payload)
};
case "FETCH_FEED":
return {
...state,
feed: action.payload
};
case "CREATED_POST":
return {
...state,
creatingPost: false,
createdPost: true
};
case "UPDATE_FEED":
return {
...state,
feed: action.payload,
feedUpdated: true
};
case "GET_POST_DATA":
return {
...state,
postDetails: action.payload
};
case "RESET_FEED_UPDATED":
return {
...state,
feedUpdated: false
};
case "RESET_CREATED_POST":
return {
...state,
createdPost: false
};
case "LIKED_POST":
const { postDetails } = state;
postDetails.liked = true;
postDetails.likes += 1;
return {
...state,
postDetails: postDetails
};
case "UNLIKED_POST":
const postDetails1 = state.postDetails;
postDetails1.liked = false;
postDetails1.likes -= 1;
return {
...state,
postDetails: postDetails1
};
case "CLEAR_POST_DATA":
return initialState;
default:
return state;
}
};
export default postsReducer;
postsActions.js
import Axios from "axios";
import moment from "moment";
import store from "../store";
export const createPost = postData => {
return (dispatch, getState) => {
dispatch({ type: "CREATING_POST" });
Axios.post("/api/post/new", {
imageUrl: postData.imageUrl,
caption: postData.caption
})
.then(res => {
dispatch({ type: "CREATED_POST" });
dispatch({ type: "ADD_POST", payload: res.data.post });
})
.catch(err => {
console.log(err);
});
};
};
export const fetchFeed = () => {
return (dispatch, getState) => {
Axios.get("/api/user/feed")
.then(res => {
var feed = res.data.feed;
const state = store.getState();
const likedPosts = state.user.userData.likedPosts;
for (var i = 0; i < feed.length; i++) {
for (var j = 0; j < feed.length - i - 1; j++) {
if (moment(feed[j + 1].createdAt).isAfter(feed[j].createdAt)) {
var temp = feed[j];
feed[j] = feed[j + 1];
feed[j + 1] = temp;
}
}
}
for (var i = 0; i < feed.length; i++) {
if (likedPosts.indexOf(feed[i]._id) > -1) {
feed[i]["liked"] = true;
} else {
feed[i]["liked"] = false;
}
}
console.log(feed);
dispatch({ type: "FETCH_FEED", payload: feed });
})
.catch(err => {
console.log(err);
});
};
};
export const likePost = postId => {
return (dispatch, getState) => {
Axios.put("/api/post/like", { postId: postId })
.then(res => {
const feed = store.getState().posts.feed;
feed.forEach(post => {
if (post._id === postId) {
post.liked = true;
}
});
dispatch({ type: "UPDATE_FEED", payload: feed });
dispatch({ type: "LIKED_POST", payload: res.data.postId });
})
.catch(err => {
console.log(err);
});
};
};
export const unlikePost = postId => {
return (dispatch, getState) => {
Axios.put("/api/post/unlike", { postId: postId })
.then(res => {
const feed = store.getState().posts.feed;
feed.forEach(post => {
if (post._id === postId) {
post.liked = false;
}
});
dispatch({ type: "UPDATE_FEED", payload: feed });
dispatch({ type: "UNLIKED_POST", payload: res.data.postId });
})
.catch(err => {
console.log(err);
});
};
};
export const getPostData = postId => {
return (dispatch, getState) => {
Axios.get(`/api/post/${postId}`)
.then(res => {
const likedPosts = store.getState().user.userData.likedPosts;
if (likedPosts.indexOf(postId) > -1) {
res.data.post["liked"] = true;
} else {
res.data.post["liked"] = false;
}
dispatch({ type: "GET_POST_DATA", payload: res.data.post });
})
.catch(err => {
console.log(err);
});
};
};
export const resetFeedUpdated = () => {
return (dispatch, getState) => {
dispatch({ type: "RESET_FEED_UPDATED" });
};
};
export const resetCreatedPost = () => {
return (dispatch, getState) => {
dispatch({ type: "RESET_CREATED_POST" });
};
};
Upvotes: 3
Views: 1478
Reputation: 7110
When working with Redux, never forget the three principles
You have mutated existing state in your reducer functions. This doesnt trigger componentdidupdate because, connect method ( it checks mapStateToProps) treats that there is nothing that changed (It checks reference and since reference didnt change Component is not invoked).
You can use Object.assign or use spread operator which helps to make your reducers return a new object.
Change your Liked and unlinked posts reducer functions to return a new object instead of mutating existing object.
@azundo added how your code should be to achieve what you need.
Upvotes: 0
Reputation: 1737
You should check, if the comparison if (prevProps.postDetails !== this.props.postDetails)
ever hits. Because with the like function you only change properties of the same object, the comparison will fail, because it's still the same object reference for postDetails
. Try to return a new object in your reducer:
case "LIKED_POST":
const { postDetails } = state;
postDetails.liked = true;
postDetails.likes += 1;
return {
...state,
postDetails: {
...postDetails
},
}
Also if you're not changing anything of the object inside the component but in Redux store why not use the component property directly? You can remove the state object and the componentDidUpdate
. Also you could refactor it to a function component.
render() {
const { postDetails: postData } = this.props;
...
}
Upvotes: 1
Reputation: 6052
Your LIKED_POST
and UNLIKED_POST
reducer cases are not pure - they are are mutating the existing postDetails
object in the state and putting it back into state
so connect
is optimizing and not re-rendering when it does a shallow equals comparison on postDetails
from the previous and next props in componentShouldUpdate
. Make sure you're creating a completely new value for postDetails
like:
case "LIKED_POST":
const { postDetails } = state;
const newPostDetails = {
...postDetails,
liked: true,
likes: postDetails.likes + 1,
};
return {
...state,
postDetails: newPostDetails
};
Upvotes: 2