Redux state changes but not reflected in component - reactjs

I'm trying to implement a shopping cart using redux. here is my cart-reducer:
export const cartReducer = (
state = { cartItems: JSON.parse(localStorage.getItem("cartItems") || "[]")},
action) => {
switch (action.type) {
case ADD_TO_CART:
return { ...state,cartItems: action.payload };
}}
Here is the component where I want to show the updated state value accessing it using props in connect and update the cartItems.length after every state update
class Cartsidebar extends Component {
constructor(props) {
super(props);
this.state = {
grandTotal: '',toggle:true
}
}
handleHide(){
this.setState({ toggle: !this.state.toggle})
}
render() {
const {cartItems}=this.props;
console.log(cartItems);
return (
<div>
{cartItems.length}
</div>
)
}
}
export default connect(
(state) => ({
cartItems: state.cart.cartItems,
}),
{ incrementToCart, decreaseToCart, removeFromCart }
)(Cartsidebar);
States are updating fine and state-difference is also showing in redux-dev-tools on every update of redux state but it is not reflecting in cart component.what am i doing wrong here?Thanks in advance.
EDIT:
this is function that execute on add to cart button onclick event:
handleAddToCart=(p)=>{
const cartItems = store.getState().cart.cartItems;
let alreadyExists = false;
cartItems.forEach((x) => {
if (x.discountPer === p.discountPer) {
alreadyExists = true;
}
});
if (!alreadyExists) {
cartItems.push({ ...p });
}
store.dispatch(addToCart(cartItems));
}
And addToCart action creator looks like this:
export const addToCart = (cartItem) => {
return({
type: ADD_TO_CART,
payload: cartItem,
});
};

Issues
You are mutating the state object. You are accessing a reference to the cart array in state and directly pushing into it.
You aren't leveraging the power of Redux and reducers properly.
code
handleAddToCart = (p) => {
const cartItems = store.getState().cart.cartItems; // cartItems is reference to state
let alreadyExists = false;
cartItems.forEach((x) => {
if (x.discountPer === p.discountPer) {
alreadyExists = true;
}
});
if (!alreadyExists) {
cartItems.push({ ...p }); // mutation!!
}
store.dispatch(addToCart(cartItems));
}
Solution
Pass the item you want to add to the cart in the action and move all the logic to update the cart into your reducer.
UI
handleAddToCart = (p) => {
this.props.addToCart(p);
}
...
export default connect(
(state) => ({
cartItems: state.cart.cartItems,
}),
{ addToCart, incrementToCart, decreaseToCart, removeFromCart }
)(Cartsidebar);
reducer
case ADD_TO_CART:
const { payload } = action;
const found = state.cartItems.find(item => item.discountPer === payload.discountPer);
if (found) {
return state;
}
return {
...state,
cartItems: state.cartItems.concat(payload),
};

What you are doing in handleAddToCart is a big no no, and goes against the pattern that Redux tries to enforce. I made some changes to your logic to make it easier, and updated the reducer. In theory, if you make these changes, it should work.
handleAddToCart:
handleAddToCart = (p) => {
const cartItems = store.getState().cart.cartItems;
for (const item of cartItems) {
if (item.discountPer === p.discountPer) {
return;
}
}
store.dispatch(addToCart({ ...p }));
};
reducer:
export const cartReducer = (
state = { cartItems: JSON.parse(localStorage.getItem("cartItems") || "[]") },
action
) => {
switch (action.type) {
case ADD_TO_CART:
return { ...state, cartItems: [...state.cartItems, action.payload] };
}
};

Related

Difference in the value received in this.props when using mapStateToProps (react-redux) vs store.getState()

I am facing an issue in my code base so I have made a sample code to demonstrate the issue.
link for the codesandbox code
App.js
import React, { Component } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { handleDataInit, handlePageChange, handleDataAdded } from './appDataAction';
import First from './First';
import Second from './Second';
import { reduxStore } from "./store";
class App extends Component {
handleChange = (pageNumber, pageTitle) => {
let data = {
val1: "val1",
val2: "val2",
val3: "val3"
}
this.props.handleDataAdded(data);
console.log("app Data", this.props.appData);
console.log('app data in redux store ', reduxStore.getState().appData);
this.props.handlePageChange({ pageNumber, pageTitle });
}
render() {
return (
<div>
<button onClick={() => this.handleChange(1, "first_page")}>1</button>
<button onClick={() => this.handleChange(2, "second_page")}>2</button>
{
this.props.appData.pageNumber === 1 ?
<First />
:
<Second />
}
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('map state to props state value is ', state);
return ({
appData: state && state.appData
})
}
const mapDispatchToProps = (dispatch) => {
return ({
handleDataInit: (data) => dispatch(handleDataInit(data)),
handlePageChange: (newPage) => dispatch(handlePageChange(newPage)),
handleDataAdded: (data) => dispatch(handleDataAdded(data))
})
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
screenshot for the two console.log
browser console log:
appDataAction.js
export const handleDataInit = (data) => {
return ({
type: "data_init",
payload: data
});
}
export const handlePageChange = (newPage) => {
return ({
type: "page_change",
payload: newPage
});
}
export const handleDataAdded = (data) => {
return ({
type: "data_added",
payload: data
});
}
appDataReducer.js
const initialState = {
pageNumber: 1,
pageTitle: "first_page",
}
export const appDataReducer = (state = initialState, action) => {
switch (action.type) {
case "data_init":
if (Object.keys(state).length > 2) {
return state
}
else {
let newState = Object.assign({}, state, action.payload);
// console.log("new state in init ", newState);
return newState;
}
case "page_change":
// console.log('action.payload', action.payload);
let newState2 = {
...state,
pageNumber: action.payload.pageNumber,
pageTitle: action.payload.pageTitle
}
// console.log('new state is ', newState2);
return newState2;
case "data_added":
let newState3 = Object.assign({}, state, action.payload);
// console.log("new state in data added ", newState3);
return newState3;
default:
return state;
}
}
From react-redux documentation
The first argument to a mapStateToProps function is the entire Redux store state (the same value returned by a call to store.getState()).
can somebody explain why there is difference in the two console's.
I have debugged and found out that after return from reducer mapStateToProps is called and it gets the updated value of state
then why is this.props.appData is not up to date in the handleChange function.
I believe it could be something related to dirty state but if it is proper for getState() in the function it should be for this.props.appData too.

how to pass products id with redux count in react native for ecommerce like addtocart function

making an eCommerce like platform and here I want to add_to_cart and show the count on the Icon. and and with count show the product as per the id which stored in array
this is my reducers :-
const cartItems = (state = [], action) => {
switch (action.type) {
case 'ADD_TO_CART':
return [...state, action.payload]
case 'REMOVE_FROM_CART':
return state.filter(cartItem => cartItem.id !== action.payload.id)
}
return state
}
export default cartItems
in which i am increasing the count as per the item seleted
const mapStateToProps = reduxStore => {
return {
cartItems: reduxStore
}
}
const mapDispatchToProps = (dispatch) => {
return {
addItemToCart: (product) => dispatch({ type: 'ADD_TO_CART', payload: product })
}
}
export default connect(mapStateToProps , mapDispatchToProps)(ContentPage)
only I am getting the count not the cart items and I want to pass this.state.data.name && this.state.data.img which getting from the URL!
For passing in additional props if that is what you are asking, mapStateToProps also takes an optional props
const mapStateToProps = (reduxStore, ...otherProps) => {
return {
cartItems: reduxStore
}
}
you can reference the data through this.props.cartItems
(Edited) Try updating the state like this:
const initialState = {
cartItems: [],
};
const cartItems = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
return { ...state, cartItems: state.cartItems.push(action.payload) };
case 'REMOVE_FROM_CART':
return {
...state,
cartItems: state.filter(cartItem => cartItem.id !== action.payload.id),
};
return state;
}
};
And to have access to cartItems in the props:
const mapStateToProps = reduxStore => {
return {
cartItems: reduxStore.cartItems
}
}

Change in state not being rendered with object.map

The state of the redux store is changing as it should but cannot get the object.map function to re-render the new state. Getting the following error: "TypeError: Cannot read property 'map' of undefined"
Confirmed that data in actions.js is correct, confirmed that data in reducer.js is correct, confirmed state change in state.PrepInfos is correct.
Form:
class PrepInfos extends Component {
render(){
const{ PrepInfos } = this.props;
return(
<Form>
{PrepInfos.map(prepInfo => <PrepInfo key={prepInfo.id} id={prepInfo.id} type={prepInfo.type} quantity={prepInfo.quantity} description={prepInfo.description} />)}
</Form>
);
}
}
const mapStateToProps = state => ({
PrepInfos: state.recipeForm.PrepInfos.PrepInfos,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(PrepInfos);
Actions:
export const H_CHANGE = 'H_CHANGE';
export function hChange(event) {
const form = ({
value: event.target.value,
name: event.target.name,
id: event.target.id,
});
return ({
type: 'H_CHANGE',
data: form,
});
}
Reducer:
import { H_CHANGE } from './PrepInfo/actions';
const initialState = {
PrepInfos: [{id:0, type:"makes", quantity:30, description:"slices"}, {id:1, type:"chill", quantity:15, description:"minutes"}],
};
export default function(state = initialState, action){
const { type, data } = action;
switch(type) {
case H_CHANGE:
return state.PrepInfos.map(prepInfo => {
if (prepInfo.id == data.id) {
return {...prepInfo, [data.name]: data.value}
};
return prepInfo;
});
default:
return state;
}
}
Corrected Reducer:
return Object.assign({}, state, {
PrepInfos: state.PrepInfos.map(prepInfo => {
if (prepInfo.id == data.id) {
return {...prepInfo, [data.name]: data.value}
};
return Object.assign({}, prepInfo, {});
})
})
Expecting to re-render the new state, instead getting TypeError: Cannot read property 'map' of undefined
The bug is caused by mutating state in the reducer
// this is mutating the PrepInfos property in state
return state.PrepInfos.map(prepInfo => {
if (prepInfo.id == data.id) {
return {...prepInfo, [data.name]: data.value}
};
return prepInfo;
});
// this is creating and returning a new obj for state and the PrepInfos key in state
return {
...state,
PrepInfos: state.PrepInfos.map(prepInfo => {
if (prepInfo.id == data.id) {
return {...prepInfo, [data.name]: data.value}
};
return prepInfo;
}

Only Mapping Nested Object of State to Props won't update Component

I use mapStateToProps to get an nested Object from an object by Id. The problem is, the props don't get updated and componentDidUpdate won't fire when the redux store state changes.
Here are my reducers:
export const programmReducers = (state = initialState, action) => {
let programms = state.programms;
switch (action.type) {
case actionTypes.FETCH_CATEGORIES:
return Object.assign({}, state, {
categories: action.payload
})
case actionTypes.FETCH_PROGRAMM:
programms[action.payload.id] = action.payload;
console.log(programms);
return {
...state,
programms: Object.assign({}, programms)
}
case actionTypes.FETCH_PROGRAMM_COMPONENTS:
programms[action.programmId].components = action.payload;
console.log('Added Components')
return {
...state,
programms: Object.assign({}, programms)
}
case actionTypes.FETCH_PROGRAMM_SECTIONS:
programms[action.programmId].sections = action.payload;
console.log('Added Sections')
return {
...state,
programms: Object.assign({}, programms)
}
default:
return state
}
}
Here is my components:
class ProgrammPage extends Component {
static async getInitialProps({ store, query: {id} }) {
if (!store.getState().programm.programms[id]) {
console.log('Programm not! found');
await store.dispatch(loadProgramm(id));
await store.dispatch(loadProgrammComponents(id));
} else {
console.log('Programm found')
}
return {
programmId: id
}
}
constructor(props) {
super(props);
if (this.props.user) {
console.log('Loading init!');
this.props.loadProgrammComponents(this.props.programmId)
this.props.loadProgrammSections(this.props.programmId);
}
}
componentDidUpdate(prevProps) {
console.log('Update')
if (!prevProps.user && this.props.user) {
console.log('Loading update');
this.props.loadProgrammComponents(this.props.programmId);
this.props.loadProgrammSections(this.props.programmId);
}
}
render() {
return (
<div>
<h1>Programm</h1>
<h2>{this.props.programm.name}</h2>
<h2>{this.props.programm.id}</h2>
<h3>Components: {this.props.programm.components ? this.props.programm.components.length : 'None'}</h3>
<h3>Sections: {this.props.programm.sections ? this.props.programm.sections.length : 'None'}</h3>
<br></br>
<h1>User: { this.props.user ? this.props.user.uid : 'None'}</h1>
<button onClick={() => this.props.loadProgramm('ProgrammLevel2')}>Load Programm</button>
<button onClick={() => this.props.loadProgrammComponents(this.props.programmId)}>Load Components</button>
</div>
)
}
}
function mapStateToProps(state, ownProps) {
return {
programm: state.programm.programms[ownProps.programmId],
// programms: state.programm.programms <--- Fixed the problem
user: state.auth.user
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
loadProgramm,
loadProgrammComponents,
loadProgrammSections
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProgrammPage)
When the Reducer for FETCH_PROGRAMM_COMPONENTS or FETCH_PROGRAMM_SECTIONS changes the redux state, componentDidUpdate isn't called and the component doesn't dispay the changes.
The problem seems to be related to the mapStateToPropsmethod, because, when I add programms: state.programm.programms everything works fine. However I don't need the whole programms object.
Why are doesn't the component recognize that the programm has updated when I map only a nested object to my props?
Your problem is within the programmReducers, your component doesn't rerender because you don't change the state.
After changing mapStateToProps you need to make changes in your component.
The next code probably breaks when you change programms: state.programm.programms to programm: state.programm.programms[ownProps.programmId]
export const programmReducers = (state = initialState, action) => {
let programms = state.programms;
...
}
So I'm guessing your reducers aren't doing what you intended.

State is undefined in mapStateToProps

I've been trying to retrieve the new state from my vitaminReducer() reducer function, and connect it through mapStateToProps. But when I console.log the state, I get back "the state is {vitamin: undefined}".
This is the Vitamins component where I'm calling mapStateToProps()
(Vitamins.js)
componentDidMount() {
this.props.fetchVitamins();
}
function mapStateToProps(state) {
return {
vitamin: state,
}
};
console.log('the state is', mapStateToProps());
export default connect(mapStateToProps, { fetchVitamins })(Vitamins);
(reducers.js)
function vitaminReducer(state = [], action) {
switch(action.type) {
case FETCH_VITAMINS_SUCCESS:
return [
...state,
action.payload.vitamins
];
default:
return state;
}
}
const reducers = combineReducers({
vitamin: vitaminReducer,
});
I have the data coming through an Express server. I've console logged "vitamins" here and I get the data back, so I know that's not the issue.
(actions.js)
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(handleErrors)
.then(res => res.json())
.then(micros => {
dispatch(fetchVitaminsSuccess(micros));
const vitamins = micros.vitamins;
}
)};
};
export const FETCH_VITAMINS_SUCCESS = 'FETCH_VITAMINS_SUCCESS';
export const fetchVitaminsSuccess = vitamins => ({
type: FETCH_VITAMINS_SUCCESS,
payload: vitamins
});
If I do: "return { vitamin: state.vitamin, }" instead of "return { vitamin: state, }", I get back "TypeError: Cannot read property 'vitamin' of undefined". But that's what I called vitaminReducer in my combineReducers() function at the bottom of reducers.js, so I thought that was the right way to do it.
Thank you everyone for your input! I was able to get it working.
I ditched the mapStateToProps() and instead did this
(Vitamins.js)
componentDidMount() {
this.props.fetchVitamins();
}
renderData() {
const { vitamins } = this.props.vitamins;
return vitamins.map((micro, index) => {
return (
<option value={micro.value} key={index}>{micro.name}</option>
)
})
}
export default connect(
state => ({
vitamins: state.vitamins
}),
{
fetchVitamins
},
)(Vitamins);
I set the dispatch action inside of the fetchVitamins() function
(actions.js)
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(handleErrors)
.then(res => res.json())
.then(micros => {
dispatch({
type: "RECEIVE_VITAMINS",
payload: micros.vitamins
});
}
)};
};
export const RECEIVE_VITAMINS = 'RECEIVE_VITAMINS';
In reducers I set the initialState to the vitamins array, and passed the new state of micros.vitamins from my RECEIVE_VITAMINS action
(reducers.js)
const initialState = {
vitamins: [],
}
function vitaminReducer(state = initialState, action) {
switch(action.type) {
case RECEIVE_VITAMINS:
return {
...state,
vitamins: action.payload
};
default:
return state;
}
}
const reducers = combineReducers({
vitamins: vitaminReducer,
});
Thanks everyone for your help! Let me know if you have any other suggestions :D

Resources