The reducers and actions are being called and working properly but my react components arent being changed after state is being changed.
This is my actions called cart.js:
import { ADD_TO_CART, REMOVE_FROM_CART } from './types';
export const addToCart = item => dispatch => {
dispatch({
type: ADD_TO_CART,
payload: {
item
}
});
}
export const removeFromCart = uid => dispatch => {
dispatch({
type: REMOVE_FROM_CART,
payload: {
uid
}
});
}
This is my reducers file called cart.js:
import { ADD_TO_CART, REMOVE_FROM_CART } from '../actions/types';
const initialState = [];
//Cannot mutate array in reducer
export default function(state = initialState, action){
const { type, payload } = action;
switch(type){
case ADD_TO_CART:
for(var i = 0; i < state.length; i++){
if(state[i].item.uid === payload.item.uid){
state[i].item.qty ++;
return [...state];
}
}
return [...state, payload];
case REMOVE_FROM_CART:
for(var j = 0; j < state.length; j++){
if(state[j].item.uid === payload.uid){
state[j].item.qty = 1;
}
}
return state.filter(cartItem => cartItem.item.uid !== payload.uid);
default:
return state;
}
}
My component files:
Cart.js:
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import { removeFromCart } from '../actions/cart';
import CartItem from './CartItem';
const Cart = ({ cart }) => {
return (
<div>
<h1>Cart</h1>
{cart.map(cartItem => (
<div style={{backgroundColor: 'blue'}}>
<CartItem cartItem={cartItem.item} key={cartItem.item.uid} />
</div>
))}
</div>
)
}
const mapStateToProps = state => ({
cart: state.cart
})
export default connect(
mapStateToProps,
{ removeFromCart }
)(Cart);
CartItem.js:
import React from 'react'
import { removeFromCart } from '../actions/cart';
import { connect } from 'react-redux';
const CartItem = ({ cartItem, removeFromCart }) => {
const handleRemoveClick = () => {
console.log('clicked', cartItem.uid);
removeFromCart(cartItem.uid);
}
return (
<div onClick={handleRemoveClick}>
{cartItem.name}
{cartItem.qty}
</div>
)
}
export default connect(
null,
{ removeFromCart }
)(CartItem);
Im trying to learn redux using react and any help is appreciated. Are for loops allowed in reducers? Im not sure why it isnt updating, the redux devtools shows everything is working. Specifically the part that isnt working is the item quantity is not being updated in component. When i remove an item it works.
Don't mutate the redux state in reducers. Copy the state and mutate it.
const newState = [...state];
//mutate newState;
return newState;
Related
I build a MERN stack ecommerce using redux. In the part of cart components I add product to the cart and also to localStorage. When I refresh the page the items disappear from the page but it is still in localStorage and I can't find the problem.
This is my cart reducer code:
import { ADD_TO_CART } from "../constants/cartConstants";
export const cartReducer = (state = { cartItems: [] }, action) => {
switch (action.type) {
case ADD_TO_CART:
const item = action.payload;
const isItemExist = state.cartItems.find(
(i) => i.product === item.product
);
if (isItemExist) {
return {
...state,
cartItems: state.cartItems.forEach((i) =>
i.product === isItemExist.product ? item : i
),
};
} else {
return {
...state,
cartItems: [...state.cartItems, item],
};
}
default:
return state;
}
};
and this is my store initialState code:
const initialState = {
cart: {
cartItems: localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [],
},
};
const store = configureStore(
{ reducer },
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
cart.jsx
import React, { Fragment, useEffect, useState } from "react";
import "./Cart.css";
import { CartItems } from "../";
import { useDispatch, useSelector } from "react-redux";
import { addItemsToCart } from "../../actions/cartActions";
const Cart = () => {
const dispatch = useDispatch();
const { cartItems } = useSelector((state) => state.cart);
const increaseQuantity = (id, quantity, stock) => {
const newQty = quantity + 1;
if (stock < quantity) {
return;
}
dispatch(addItemsToCart(id, newQty));
};
return (
<Fragment>
<div className="cart__page">
<div className="cart__header">
<p>Product</p>
<p>Quantity</p>
<p>Subtotal</p>
</div>
{cartItems &&
cartItems?.map((item) => (
<div key={item?.product} className="cartContainer">
<CartItems item={item} />
<div className="cart__Input">
<button>+</button>
<input type="number" readOnly value={item?.quantity} />
<button>-</button>
</div>
<p className="Cart__subtotal">
{`$${item?.price * item?.quantity}`}
</p>
</div>
))
}
I'm trying to use useEffect hook but the data come by redux doesn't save in localStorage.
The configureStore function takes only a single configuration object that takes reducer, middleware, devTools, preloadedState, and enhancers properties.
See configureStore.
It appears you are correctly accessing the persisted state from localStorage, but then not passing the initial state correctly to the store configurator.
import { configureStore } from '#reduxjs/toolkit';
const initialState = {
cart: {
cartItems: JSON.parse(localStorage.getItem("cartItems")) ?? [],
},
};
const store = configureStore({
reducer,
preloadedState: initialState,
});
export default store;
If your redux state persistence needs change or grow then I'd suggest taking a look at redux-persist. If you are already familiar with Redux then this is about a 5-15 minute integration the first time.
I have a component that displays data from the state. I'm using redux for state. I want to be able to click a button and filter the state. But I'm stuck on dispatching the action from the button.
Right now I have a button that is supposed to dispatch the action but it's not being called. I'm not sure if the mapsToDispatchProps is wrong or it's something else.
Here is the actions
import { GET_POLLS, SHOW_APPROVAL } from './types';
const URL = 'https://projects.fivethirtyeight.com/polls/polls.json';
export const getPolls = () => dispatch => {
return fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
}
export const getApproval = () => ({ type: SHOW_APPROVAL })
reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return (payload.type === "trump-approval")
default:
return state
}
}
export default pollReducer;
types
export const GET_POLLS = 'GET_POLLS';
export const POLLS_LOADING = 'POLLS_LOADING';
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_APPROVAL = 'SHOW_APPROVAL';
list that displays data
import React, { Component } from 'react'
import { PollCard } from '../Components/PollCard'
// import FilterLink from './FilterLink'
import * as moment from 'moment';
import { connect } from 'react-redux'
import { getPolls, getApproval } from '../actions/index';
class PollList extends Component {
componentDidMount() {
this.props.getPolls();
}
render() {
console.log("rendering list")
const { polls } = this.props
const range = 30
var dateRange = moment().subtract(range, 'days').calendar();
var filteredPolls = polls.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange)).reverse()
return (
<React.Fragment>
<button onClick={getApproval}>
Get Approval
</button>
{console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
polls: state.polls
});
const mapDispatchToProps = {
getApproval
};
export default connect(
mapStateToProps,
mapDispatchToProps,
{ getPolls, getApproval }
)(PollList);
// export default PollList;
Your mapDispatchToProps() appears to be configured incorrectly. You need to define a function that returns an object, defining a key-value pair for each action you want to make available as a prop in your component.
const mapDispatchToProps = (dispatch) => {
return {
getApproval: () => {
dispatch(getApproval())
},
getPolls: () => {
dispatch(getPolls())
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProp)(PollList);
Now getPolls is available as prop and you can use it in componentDidMount()
componentDidMount() {
this.props.getPolls();
}
You should also create an onClick handler for your getApproval action
handleClick = () => {
this.props.getApproval()
}
And then connect it to your onClick event-listener
<React.Fragment>
<button onClick={this.handleClick}>
Get Approval
</button>
console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
Action File
export const getPolls = () => dispatch => {
fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
.catch(errors => {
dispatch({ type: "GET_ERRORS", payload: errors.response.data })
})
}
Reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return state.filter((poll) => {
return poll.type === "trump-approval"
})
case "GET_ERRORS":
return payload
default:
return state
}
}
export default pollReducer;
You are not calling the action function.
// Either destructure it
const { polls, getApproval } = this.props;
<button onClick={getApproval}>
Get Approval
</button>
// Or use this.props.function
<button onClick={this.props.getApproval}>
Get Approval
</button>
// You don't need this
const mapDispatchToProps = {
getApproval
};
// You don't need this
const mapStateToProps = state => {
return {polls: state.polls};
};
export default connect(
mapStateToProps,
// Doing this is easier, cleaner & faster
{ getPolls, getApproval }
)(PollList);
Here you are doing it correctly;
componentDidMount() {
this.props.getPolls();
}
I am working on a social network app and I want to toggle the like count on clicking(it should increment by 1 on first click and should go back to null to when pressed again) for a particular post. But now when i click on the like button, nothing happens and the screen gets vanish. I am unable to get what is wrong with my code.
Here are my files-> action creator
export const fetchPosts = () => async dispatch => {
const request = await axios.get(`${ROOT_URL}/post`, {
headers: { Authorization: `${token}` }
});
dispatch({
type: FETCH_POSTS,
payload: request
});
};
export const incrementLikesCount = id => {
return {
type: INCREMENT_LIKES_COUNT,
payload: id
};
};
index.js(reducer)
import auth from "./authReducer";
import user from "./userReducer";
import post from "./postReducer";
export default combineReducers({
auth,
user,
post,
form: formReducer
});
postreducer.js
import _ from "lodash";
import { FETCH_POSTS, INCREMENT_LIKES_COUNT } from "../actions/types";
const initialState = {
postDetail: "",
likesCount: null
};
const post = (state = initialState, action) => {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
postDetail: _.mapKeys(action.payload.data.data, "_id")
};
case INCREMENT_LIKES_COUNT:
return _.values(state.postDetail)
.reverse()
.map(post => {
if (action.payload === post._id) {
if (state.likesCount === null) {
console.log("I got executed");
return { ...state, likesCount: state.likesCount + 1 };
} else {
return {
...state,
likesCount: null
};
}
} else {
return {
state
};
}
});
default:
return state;
}
};
export default post;
and my react Component
import _ from "lodash";
// import uuid from "uuid";
import { connect } from "react-redux";
import React, { Component } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faHeart,
faCommentAlt,
faShareAlt
} from "#fortawesome/free-solid-svg-icons";
import { fetchPosts, incrementLikesCount } from "../../../actions/FeedPost";
import "./FeedPosts.css";
class FeedPosts extends Component {
componentDidMount() {
if (!this.props.fetchPosts) {
return <div>Loading...</div>;
}
this.props.fetchPosts();
}
renderPosts = () => {
return _.values(this.props.post)
.reverse()
.map(post => (
<div key={post._id} className="post-content">
<img
src={require("../../../img/blue.jpeg")}
alt="user"
className="user-image"
/>
<span>{post.postBy}</span>
<span>{post.userDesignation}</span>
<li>{post.postText}</li>
<div className="fontawesome-icons">
<div className="like-font">
<FontAwesomeIcon
icon={faHeart}
onClick={() => this.props.incrementLikesCount(post._id)}
/>
<span>{this.props.likesCount}</span>
</div>
<div className="comment-font">
<FontAwesomeIcon icon={faCommentAlt} />
</div>
<div className="share-font">
<FontAwesomeIcon icon={faShareAlt} />
</div>
</div>
</div>
));
};
render() {
return (
<div>
<ul className="posts">{this.renderPosts()}</ul>
</div>
);
}
}
const mapStateToProps = state => ({
post: state.post.postDetail,
likesCount: state.post.likesCount
});
export default connect(
mapStateToProps,
{ fetchPosts, incrementLikesCount }
)(FeedPosts);
So, Basically my question is how can I increase the like count just for a particular post, because I was able to toggle the like button but it was increasing the like count of all the posts.
The following should kind of work but it would be easier to have state.posts as array instead of converting from array to object and object to array every time.
To be sure it'll work you need to show the code where you set state.posts
case INCREMENT_LIKES_COUNT:
return {
...state,
likesCount:state.likesCount+1,
//not sure why posts need to be an object instead of it being an array
posts:Object.entries(state.posts).reduce(
(result,[key,value])=>{
if(value._id===action.payload){
//you probably didn't set the initial likes but the reducer
// where you set state.posts isn't in your question
result[key]= {...value,likes:value.likes+1};
}else{
result[key]=value;
}
return result;
},
{}
)
}
Although after seeing this again I realize the posts is an object where the id is the key so you can make it simpler:
case INCREMENT_LIKES_COUNT:
return {
...state,
likesCount:state.likesCount+1,
//not sure why posts need to be an object instead of it being an array
posts:{
...state.posts,
[action.payload]:{
...state.posts[action.payload],
likes:state.posts[action.payload].likes+1
}
}
}
I'm very new to React.js and Redux.
I'm trying to build a very simple shopping cart application.
What I want is if you hit on an item (eg :- banana) It should appear in the cart.
(It should change the state of the cartReducer.js)
But instead of pushing the item to the reducer state it pushes something else.
What is the reason for this error?
This is my code.
cartReducer
import {ADD_TO_CART} from '../actions/index'
const initialState =[
]
export default (state = initialState,action)=>{
console.log("ACTION PAYLOAD",action.payload)
switch(action.type){
case ADD_TO_CART:
return[...state,action.payload]
default:
return state
}
}
Item component
import React, { Component } from "react";
import { connect } from "react-redux";
import {addToCart} from '../../actions/index'
export class Etem extends Component {
showItems = () => {
const { items, addToCartAction } = this.props;
console.log("ITEMS", items);
return items.map(items => <div key={items.id} onClick={addToCartAction}>{items.name}</div>);
};
render() {
return (
<div>
<h1>Items</h1>
<div>{this.showItems()}</div>
</div>
);
}
}
// export default items;
const mapStateToProps = reduxState => ({
items: reduxState.items
});
const mapDispatchToProps = dispatch => ({
addToCartAction: item => dispatch(addToCart(item))
});
export default connect(mapStateToProps, mapDispatchToProps)(Etem);
Action
export const ADD_TO_CART = 'ADD_TO_CART';
export const addToCart=(item) =>{
console.log("ITEMMMMMMMMMM",item)
return(
{
type:ADD_TO_CART,
payload:item,
}
)
}
<div key={items.id} onClick={addToCartAction}> this will pass the click event to addToCartAction instead of item.
Try this:
return items.map(item => (
<div key={item.id} onClick={() => addToCartAction(item)}>
{item.name}
</div>
));
Can you change the mapStateToProps
const mapStateToProps = reduxState => ({
items: reduxState.cartReducer.items
})
I'm trying to get to grips with Redux + React - I have hooked up the relevant bits of Redux with connect() for a small todo app but I cannot for the life of me get the component to update and show the reflected store changes. The store state does update however the component will not. Here are the relevant bits in my code:
actionTypes.js
export const ADD_TODO = "ADD_TODO";
export const DELETE_TODO = "DELETE_TODO";
export const CLEAR_TODO = "CLEAR_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";
reducers.js
import {ADD_TODO, COMPLETE_TODO, DELETE_TODO, CLEAR_TODO} from '../actions/actionTypes';
const todoApp = (state, action) => {
let updatedState;
switch (action.type) {
case ADD_TODO:
updatedState = Object.assign({}, state);
updatedState.todo.items.push({
text: action.text,
completed: false
});
return updatedState;
case COMPLETE_TODO:
updatedState = Object.assign({}, state);
updatedState.todo.items[action.index].completed = true;
return updatedState;
case DELETE_TODO:
const items = [].concat(state.todo.items);
items.splice(action.index, 1);
return Object.assign({}, state, {
todo: {
items: items
}
});
case CLEAR_TODO:
return Object.assign({}, state, {
todo: {
items: []
}
});
default:
return state;
}
};
export default todoApp;
actions.js
import {ADD_TODO, COMPLETE_TODO, DELETE_TODO, CLEAR_TODO} from './actionTypes.js';
export const addTodoCreator = (text) => {
return {
type: ADD_TODO,
text: text,
completed: false
}
};
export const completeTodo = (index) => {
return {
type: COMPLETE_TODO,
index: index
}
};
export const deleteTodo = (index) => {
return {
type: DELETE_TODO,
index: index
}
};
export const clearTodo = (index) => {
return {
type: CLEAR_TODO,
index: index
}
};
AddTodoContainer.js
import { connect } from 'react-redux';
import TodoList from '../components/TodoList';
const mapStateToProps = (state, ownProps) => {
return {
todo: state.todo
}
};
export default connect(mapStateToProps)(TodoList);
TodoListContainer.js
import { connect } from 'react-redux';
import {addTodoCreator} from '../actions/actions';
import AddTodo from '../components/AddTodo';
const mapStateToProps = (state) => {
console.log(state);
return {
todo: state.todo
}
};
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (text) => {
const action = addTodoCreator(text);
dispatch(action);
},
}
};
export default connect(mapStateToProps, mapDispatchToProps)(AddTodo);
AddTodo.js
import React from 'react'
const handler = (addTodo) => {
const text = document.getElementById('textInput').value;
addTodo(text);
};
const AddTodo = ({addTodo}) => {
return (
<div>
<input id="textInput" type="text" className="textInput" />
<button onClick={(handler).bind(null, addTodo)}>Add</button>
</div>
)
}
export default AddTodo
TodoList.js
import React from 'react';
import AddTodoContainer from '../containers/AddTodoContainer';
class TodoList extends React.Component {
render () {
console.log(this.props);
return (
<div>
<ul>
{this.props.todo.items.map((item) => {
return <li>
{item.text}
</li>
})}
</ul>
<AddTodoContainer/>
</div>
)
}
}
export default TodoList;
I've tried all of the suggestions under Troubleshooting and as far as I can tell I am not mutating state. The reducer is firing and I can log out the states. The code is stored here under react-fulltodo http://gogs.dev.dylanscott.me/dylanrhysscott/learn-redux
Thanks
Dylan
You're passing todo to your component and while the todo object gets updated the actual todo object in redux state is the same exact object as it was before. So react does not see the object as changed. For example:
const a = { foo: 'bar' };
const b = a;
b.foo = 'I made a change';
console.log(a==b);
// logs true because a and b are the same object
// This is exactly what's happening in React.
// It sees the object as the same, so it does not update.
You need to clone the todo object so that react sees it as a changed/new object.
In your reducer:
switch (action.type) {
case ADD_TODO:
updatedState = Object.assign({}, state);
// Shallow clone updatedState.todo
updatedState.todo = Object.assign({}, updatedState.todo);
updatedState.todo.items.push({
text: action.text,
completed: false
});
return updatedState;
Meanwhile, if you passed state.todo.items to your component you would not have to clone todo but you would have to clone items. So in the future, if you have a component that directly mapStateToProps with state.todo.items, it will have the same problem because you are not cloning the items array in ADD_TODO like you are in the DELETE_TODO reducer.