i cannot delete a product, although it seems that everything looks fine and i see in console that action of type: REMOVE_SELECTED_PRODUCT occurs, the product is still in array and seems like the state is not updated??
So lets start with productActions.js
export const removeSelectedProduct = (id) => {
return {
type: REMOVE_SELECTED_PRODUCT,
payload: id,
};
};
export const removeProduct = (id) => {
return async (dispatch) => {
dispatch(removeSelectedProduct(id));
try {
console.log(id); // this is for test, and i see in console that proper id is printed
await axios.delete(`https://fakestoreapi.com/products/${id}`);
} catch (err) {
console.log(err);
}
};
};
now the productsReducer.js
const intialState = {
products: [],
};
export const productsReducer = (state = intialState, { type, payload }) => {
switch (type) {
case SET_PRODUCTS:
return { ...state, products: payload };
case ADD_PRODUCT:
return { ...state, products: [...state.products, payload] };
case REMOVE_SELECTED_PRODUCT:
return {
...state,
products: state.products.filter((el) => el.id !== payload),
};
default:
return state;
}
};
And i use it in ProductDetails.js as an button so there is a whole code of this component:
const ProductDetails = ({ removeProduct, product }) => {
const { productId } = useParams();
const { image, title, price, category, description } = product;
const dispatch = useDispatch();
const fetchProductDetail = async (id) => {
const response = await axios
.get(`https://fakestoreapi.com/products/${id}`)
.catch((err) => {
console.log("Err: ", err);
});
dispatch(selectedProduct(response.data));
};
useEffect(() => {
if (productId && productId !== "") fetchProductDetail(productId);
}, [productId]);
return (
<div>
{Object.keys(product).length === 0 ? (
<div>...Loading</div>
) : (
<div>
<img alt={productId} src={image} />
<div>
<h1>{title}</h1>
<h2>
<p>${price}</p>
</h2>
<h3>{category}</h3>
<p>{description}</p>
<button>Add to Cart</button>
<button onClick={() => removeProduct(productId)}>Usuń</button>
</div>
</div>
)}
</div>
);
};
const mapStateToProps = (state) => {
return {
products: state.allProducts.products,
product: state.product,
};
};
const mapDispatchToProps = {
removeProduct,
selectedProduct,
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductDetails);
If anyone cant find the problem, i would be glad.
As an i said the acction and the id is printed to the console:
console output on clicking the button
Maybe there is some problem with state, and it dont update the state when i delete? Idk, please help me :(
As product has number type id and you're providing string i.e
2 !== "2"
Do this:
dispatch(removeSelectedProduct(+id));//change string to number
...
...
await axios.delete(`https://fakestoreapi.com/products/${+id}`);//similarly here
Related
I'm using React Redux and want to be able to change the title and description of a post, using the onChange method. When only using React the way you would do this is that you keep an useState which you change whenever a change occurs, but I can't seem to get it to work with using redux in react. Instead of the state changing the original title, and description remains and cannot be changed.
From what I have read the basic idea is to have a listener on the input (onChange, usually) and have that fire a redux action. You then have the action tell the reducer to make the change to the store.
I have tried doing this, but could make it work correctly. What am I doing wrong and how do you solve it? I'm also wondering how do I specify that I want to change either title or description when using onChange, or do I simply send everything in post each time a change occurs?
This is what the redux state looks like when entering a post:
{
auth: {
isSignedIn: true,
user: {
id: '624481f22566374c138cf974',
username: 'obiwan',}
},
posts: {
'62448632b87b223847eaafde': {
_id: '62448632b87b223847eaafde',
title: 'hellothere',
desc: 'its been a long time since I heard that name...',
username: 'vorbrodt',
email: 'example#gmail.com',
categories: [],
createdAt: '2022-03-30T16:32:50.158Z',
updatedAt: '2022-03-30T16:32:50.158Z',
__v: 0
}
},
}
Here is where the onChange happens.
Post.js
import { getPostById, editPost } from "../actions";
const Post = ({ getPostById, editPost, username }) => {
const [updateMode, setUpdateMode] = useState(false);
let { id } = useParams();
let post = useSelector((state) => state.posts[id]);
const handleInputChange = (e) => {
try {
editPost(e.target.value);
} catch (err) {}
};
return (
<div className="post">
<div className="post-wrapper">
{updateMode ? (
<input
type="text"
value={post.title}
className="post-title-input"
autoFocus
onChange={(e) => handleInputChange(e)}
/>
) : (
<h1 className="post-title">
{post.title}
</h1>
)}
<div className="desc-area">
{updateMode ? (
<textarea
className="post-desc-input"
value={post.desc}
onChange={(e) => handleInputChange(e)}
/>
) : (
<p className="post-desc">{post.desc}</p>
)}
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return { username: state.auth.user.username };
};
export default connect(mapStateToProps, { getPostById, editPost })(Post);
Here is the action creator:
//edit post in redux state
const editPost = (postValues) => (dispatch) => {
dispatch({ type: EDIT_POST, payload: postValues });
};
And here is the reducer which is suppose to change the state.
postReducer.js
import _ from "lodash";
import { GET_POSTS, GET_POST, CREATE_POST, EDIT_POST } from "../actions/types";
function postReducer(state = {}, action) {
switch (action.type) {
case GET_POSTS:
return { ...state, ..._.mapKeys(action.payload, "_id") };
case GET_POST:
return { ...state, [action.payload._id]: action.payload };
case CREATE_POST:
return { ...state, [action.payload._id]: action.payload };
case EDIT_POST:
//here the change should occur, not sure how to specify if title or desc should
//change
return { ...state, [action.payload._id]: action.payload };
default:
return state;
}
}
export default postReducer;
Hey there something like this should be of help
const handleInputChange = (e, key, id) => {
try {
editPost({ [key]: e.target.value, id });
} catch (err) {}
};
Usage
<textarea
className="post-desc-input"
value={post.desc}
onChange={(e) => handleInputChange(e, "title", post.id)}
/>
action
const editPost = (postValues) => (dispatch) => {
dispatch({ type: EDIT_POST, payload: postValues });
};
Reducer
case EDIT_POST:
//here we destructure the id and return the data without the id cause we //need it below
const {id, ...newData} = action.payload
const indexToUpdate = state.posts.find(post => post.id === id)
const newPostsData = [...state.posts]
//Here we update the actual object and its property that is in the state at //the specific value
newPostsData[indexToUpdate] = {...newPostData[indexToUpdate], {...newData}
return { ...state, posts: newPostsData};
I asked similar question earlier, but didn't get much back. I have two modals for user auth: join and login. Each modal has a link to the other one. Displayed login errors persist when you click on the "sign up" and switch to the join modal and vise versa. I tried to set the state.errors to empty array, but the errors still persist. I changed handleSwitch to callback. The errors array still has length. I tried using switched as part of the state, resetting it to true in handleSwitch and ternary, no result either. Can anybody suggest an alternative solution.
import React from 'react';
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
errors: [],
switched: false
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleSwitch = this.handleSwitch.bind(this);
this.mapErrors = this.mapErrors.bind(this);
this.handleErrors = this.handleErrors.bind(this);
}
componentDidMount() {
this.setState({ errors: this.props.errors})
}
componentDidUpdate(prev) {
if (prev.errors.length !== this.props.errors.length) {
this.setState( {errors: this.props.errors} )
}
}
handleInput(type) {
return (err) => {
this.setState({ [type]: err.currentTarget.value })
};
}
handleSubmit(event) {
event.preventDefault();
const user = Object.assign({}, this.state);
this.props.processForm(user)
// .then(() => this.props.history.push('/users')); //change to /videos later
}
handleSwitch() {
// debugger
this.setState({ errors: [] }, function () {
this.props.openModal('signup')
});
// debugger
}
mapErrors() {
if (this.state.errors.length) {
return this.state.errors.map((error, i) => {
return <p key={i}>{error}</p>
})
}
}
handleErrors() {
debugger
if (!this.state.switched) {
return <div className="errors">{this.mapErrors}</div>
} else {
return null;
}
};
render() {
console.log(this.state.errors)
return (
<div className="login-form">
<div>
<h2 className="login-header">Log in to Foxeo</h2>
</div>
<form>
<input className="login-email"
type="text"
value={this.state.email}
placeholder="Email address"
onChange={this.handleInput('email')}
/>
<input className="login-password"
type="password"
value={this.state.password}
placeholder="Password"
onChange={this.handleInput('password')}
/>
<div className="errors">{this.mapErrors()}</div>
{/* { this.state.switched ?
<div className="errors">{this.handleErrors()}</div> :
<div className="errors">{this.mapErrors()}</div>
} */}
<button className="login-button" onClick={this.handleSubmit}>Log in with email</button>
<div className="login-footer">Don't have an account?
{/* <button className="login-form-btn" onClick={() => this.props.openModal('signup')}>Join</button> */}
<button className="login-form-btn" onClick={ this.handleSwitch}> Join</button>
</div>
</form>
</div>
);
}
};
export default Login;
I suggest getting the new errors from the props instead of from state:
mapErrors() {
if (this.props.errors.length) {
return this.props.errors.map((error, i) => {
return <p key={i}>{error}</p>
})
Dispatching resetErrors action solved the issue. The handleSwitch method is quite simple:
handleSwitch() {
this.props.resetErrors()
this.props.openModal('signup')
}
session actions:
import * as apiUtil from '../util/session_api_util';
export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER';
export const LOGOUT_CURRENT_USER = 'LOGOUT_CURRENT_USER';
export const RECEIVE_ERRORS = 'RECEIVE_ERRORS';
export const CLEAR_ERRORS = 'CLEAR_ERRORS';
const receiveErrors = (errors) => ({
type: RECEIVE_ERRORS,
errors
})
const clearErrors = () => ({
type: CLEAR_ERRORS,
errors: []
})
const receiveCurrentUser = (user) => ({
type: RECEIVE_CURRENT_USER,
user
});
const logoutCurrentUser = () => ({
type: LOGOUT_CURRENT_USER
});
export const signup = user => dispatch => (
apiUtil.signup(user).then(user => (
dispatch(receiveCurrentUser(user))
), err => (
dispatch(receiveErrors(err.responseJSON))
))
);
export const login = user => dispatch => {
return apiUtil.login(user).then(user => {
dispatch(receiveCurrentUser(user))
}, err => (
dispatch(receiveErrors(err.responseJSON))
))
};
export const logout = () => dispatch => apiUtil.logout()
.then(() => dispatch(logoutCurrentUser()));
export const resetErrors = () => dispatch(clearErrors());
session errors reducer:
import { RECEIVE_ERRORS, RECEIVE_CURRENT_USER, CLEAR_ERRORS } from '../actions/session_actions';
const sessionErrorsReducer = (state = [], action) => {
Object.freeze(state);
switch (action.type) {
case RECEIVE_ERRORS:
return action.errors;
case CLEAR_ERRORS:
return [];
case RECEIVE_CURRENT_USER:
return [];
default:
return state;
}
};
export default sessionErrorsReducer;
I built an E-commerce site for pizza. It could be viewed here: https://chinomso1995.github.io/dodosPizza/.
I have created an orders page and On the orders page when you try to increment items, it increments in two's. I have gone through the reducer code I wrote and the context code and I seem to have written everything correctly. I can't pinpoint where the problem is coming from.
JSX code for my reducer
const Storage = (cartItems) => {
localStorage.setItem('cart', JSON.stringify(cartItems.length > 0 ? cartItems: []));
}
export const sumItems = cartItems => {
Storage(cartItems);
let itemCount = cartItems.reduce((total, product) => total + product.quantity, 0);
let total = cartItems.reduce((total, product) => total + product.price * product.quantity, 0).toFixed(2);
return { itemCount, total }
}
export const CartReducer = (state, action) => {
switch (action.type) {
case "ADD_ITEM":
if (!state.cartItems.find(item => item.id === action.payload.id)) {
state.cartItems.push({
...action.payload,
quantity: 1
})
}
return {
...state,
...sumItems(state.cartItems),
cartItems: [...state.cartItems]
}
case "REMOVE_ITEM":
return {
...state,
...sumItems(state.cartItems.filter(item => item.id !== action.payload.id)),
cartItems: [...state.cartItems.filter(item => item.id !== action.payload.id)]
}
case "INCREASE":
state.cartItems[state.cartItems.findIndex(item => item.id === action.payload.id)].quantity++
return {
...state,
...sumItems(state.cartItems),
cartItems: [...state.cartItems]
}
case "DECREASE":
state.cartItems[state.cartItems.findIndex(item => item.id === action.payload.id)].quantity--
return {
...state,
...sumItems(state.cartItems),
cartItems: [...state.cartItems]
}
case "CHECKOUT":
return {
cartItems: [],
checkout: true,
...sumItems([]),
}
case "CLEAR":
return {
cartItems: [],
...sumItems([]),
}
default:
return state
}
}
JSX code for the context
import React, { createContext, useReducer, use } from 'react';
import { CartReducer, sumItems } from './OrderReducer';
export const CartContext = createContext()
const storage = localStorage.getItem('cart') ? JSON.parse(localStorage.getItem('cart')) : [];
const initialState = { cartItems: storage, ...sumItems(storage), checkout: false };
const CartContextProvider = ({children}) => {
const [state, dispatch] = useReducer(CartReducer, initialState)
const increase = payload => {
dispatch({type: 'INCREASE', payload})
}
const decrease = payload => {
dispatch({type: 'DECREASE', payload})
}
const addProduct = payload => {
dispatch({type: 'ADD_ITEM', payload})
}
const removeProduct = payload => {
dispatch({type: 'REMOVE_ITEM', payload})
}
const clearCart = () => {
dispatch({type: 'CLEAR'})
}
const handleCheckout = () => {
console.log('CHECKOUT', state);
dispatch({type: 'CHECKOUT'})
}
const contextValues = {
removeProduct,
addProduct,
increase,
decrease,
clearCart,
handleCheckout,
...state
}
return (
<CartContext.Provider value={contextValues} >
{ children }
</CartContext.Provider>
);
}
export default CartContextProvider;
JSX code for the Product Context
export const ProductsContext = createContext()
const ProductsContextProvider = ({children}) => {
const [pizzaproducts] = useState(Pizza)
const [sideproducts] = useState(Sides);
const [dessertproducts] = useState(Desserts)
const [drinkproducts] = useState(Drinks)
return (
<ProductsContext.Provider value={{sideproducts, dessertproducts, drinkproducts, pizzaproducts}} >
{ children }
</ProductsContext.Provider>
);
}
export default ProductsContextProvider;
JSX code for a section
const Sides = ()=> {
const {sideproducts} = useContext(ProductsContext);
const {addProduct, cartItems, increase} = useContext(CartContext);
const isInCart = sideproducts => {
return !!cartItems.find(item => item.id === sideproducts.id);
}
return(
<div className={styles.Sides} id='sides'>
<h1>Sides</h1>
<div className={styles.SidesContainer}>
{sideproducts.map(side=>{
return <div className={styles.SidesCard}>
<div className={styles.ImageContainer}>
<img src={side.image} alt="sausagerollone"/>
</div>
<div className={styles.SidesHeader}>
<div>
<h1>{side.name}</h1>
<p>{side.details}</p>
</div>
<div className={styles.SidesFooter}>
<h3>₦{side.price}</h3>
{
isInCart(side) &&
<button
onClick={() => increase(side)}>Add more</button>
}
{ !isInCart(side) &&
<button onClick={()=>addProduct(side)}>
<span>from ₦{side.price}</span>
<span>Add to basket</span>
</button>}
</div>
</div>
</div> })}
</div>
</div>
)
}
export default Sides;
In the increased case of the reducer code, I increment each item by one but it does increment by two in the app itself. This also affects the total price.
I've been trying to create this search app where I can display the items in a table and delete items using react redux. However, on the initial load, the app shows a table but there is no data in the table. It's an empty table. If i search for another movie name which have more than one movie for that search term, then 2 tables would be shown but I want to show everything on the same table itself. The delete button is not working as well. Is there something wrong with my action and reducer files?
Action.js
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "./types";
const fetchMoviePendig = () => ({
type: FETCH_MOVIE_PENDING
});
const fetchMovieSuccess = json => ({
type: FETCH_MOVIE_SUCCESS,
payload: json
});
const fetchMovieError = error => ({
type: FETCH_MOVIE_ERROR,
payload: error
});
export const fetchMovie = name => {
return async dispatch => {
dispatch(fetchMoviePendig());
try {
const url = `https://jsonmock.hackerrank.com/api/movies/search/?Title=${name}`;
const response = await fetch(url);
const result = await response.json(response);
console.log(result);
dispatch(fetchMovieSuccess(result.data));
} catch (error) {
dispatch(fetchMovieError(error));
}
};
};
export const deleteEvent = id => async dispatch => {
try {
dispatch({
type: DELETE_MOVIE,
payload: id
});
} catch (err) {
console.log(err);
}
};
Reducer
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "../action/types";
const initialState = {
data: [],
loading: false,
error: ""
};
const moviesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_MOVIE_PENDING:
return {
...state,
loading: true
};
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: [...state.data, action.payload]
};
case FETCH_MOVIE_ERROR:
return {
...state,
loading: false,
error: action.payload
};
case DELETE_MOVIE:
return {
...state,
data: state.data.filter(movie => movie.id !== action.payload)
};
default:
return state;
}
};
export default moviesReducer;
App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMovie } from "./action/movieActions";
import Input from "./components/Input";
import MovieTable from "./components/MovieTable";
class App extends Component {
state = {
searchInput: "The Rain"
};
componentDidMount() {
this.props.getMovieList(this.state.searchInput);
}
_getMovie = () => {
this.props.getMovieList(this.state.searchInput);
};
_onChangeHandler = e => {
this.setState({
searchInput: e.target.value
});
console.log(this.state.searchInput);
};
render() {
const { data, loading } = this.props.movies;
return (
<div className="center">
<div>
<h2 className="center white-text">Movie Search</h2>
</div>
<div className="container">
<Input
value={this.state.searchInput}
onChange={this._onChangeHandler}
onClick={this._getMovie}
/>
<div className="row">
{loading ? (
<p>Loading</p>
) : (
data.map(item => (
<MovieTable
key={item.imdbID}
year={item.Year}
name={item.Title}
movieId={item.imdbId}
/>
))
)}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
movies: state.movies
};
};
const mapDispatchToProps = dispatch => {
return {
getMovieList: name => dispatch(fetchMovie(name))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Hello please take a look at the sandbox : https://codesandbox.io/s/prod-wind-4hgq2?file=/src/App.js
I have edited
<MovieTable
data={data.map(d => ({
year: d.Year,
name: d.Title,
movieId: d.imdbId
}))}
/>
and
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: action.payload
};
And ... Currently the delete button has no event, that's why it can't work
I see data having the following pattern:
Object {page: 1, per_page: 10, total: 1, total_pages: 1, data: Array[1]}
page: 1
per_page: 10
total: 1
total_pages: 1
data: Array[1]
0: Object
Title: "Sin in the Rain"
Year: 2006
imdbID: "tt1072449"
And you are accessing wrong properties in the component render logic, can you fix that.
Duplicate table is created the way you have written the logic.
Pass the data to MovieTable component and let it render and create the table
and fill it.
In reducer (FETCH_MOVIE_SUCCESS) you need don't need to append data you have to
replace or use the current movie data only.
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" });
};
};
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
};
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;
...
}
When working with Redux, never forget the three principles
Single Source of truth
State is ready only
Reducers must be pure functions: Reducers take previous state and some action and modifies it and returns new state. We should never mutate state. We should create new objects and return them.
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.