I need to increase property in the state, but I have no idea how I can do it
help please, I have tried two days but got many troubles
export const initialState = {
ids: [],
basket: []
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_BASKET':
if (state.ids.indexOf(action.item.id) === -1) {
// if new item add to basket
return {
ids: [...state.ids, action.item.id],
basket: [...state.basket, action.item]
}
}
else {
if (state.basket.length > 0) {
// state.basket[state.basket.length - 1].quantity += 1
}
return {
...state,
// IT'S WRONG
basket: [...state.basket, [state.basket.length - 1].quantity += 1]
}
}
default:
return state;
}
}
You mutate state (basket.quantity += 1) when it should treated as immutable.
I believe you need to rethink how you structure your state if it gets so hard to immutably update a state.
export const initialState = {
ids: [],
basket: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_TO_BASKET":
if (state.ids.indexOf(action.item.id) === -1) {
...
} else {
...
const basketsShallowCopy = [...state.basket];
const basketToUpdate = basketsShallowCopy.pop();
const updatedBasked = {
...baskedToUpdate,
quantity: basketToUpdate.quantity + 1,
};
basketsShallowCopy.push(updatedBasked);
return {
...state,
basket: basketsShallowCopy,
};
}
...
}
};
Related
so my reducer looks like that.
I want to add items to cart and if the item is already in the state it should increase the qty of that item.
I pass object to payload but it doesnt contain the quantity field so i create it manually.
But when i add more than 2 of the same item it doesnt update further.
What is the issue here?
const init = { cartItems: [] };
export const cartReducer = (state = init, action) => {
switch (action.type) {
case 'ADD_TO_CART': {
const theItem = state.cartItems.find(
(item) => item.title === action.payload.title
);
if (theItem) {
theItem.quantity++;
return { ...state };
} else {
const updatedCart = [...state.cartItems, action.payload];
return { ...state, cartItems: updatedCart };
}
}
default: {
return { ...state };
}
}
};
You cannot mutate the state, it will then not update the react component, you can try the below code for updating the quantity
const init = { cartItems: [] };
export const cartReducer = (state = init, action) => {
switch (action.type) {
case 'ADD_TO_CART': {
const itemIndex = state.cartItems.findIndex(
(item) => item.title === action.payload.title
);
if (itemIndex !== -1) {
const newCartItems = [...state.cartItems];
const newItem = {...newCartItems[itemIndex]}
newItem.quantity += 1;
newCartItems[itemIndex] = newItem;
return { ...state, cartItems: newCartItems};
} else {
const updatedCart = [...state.cartItems, action.payload];
return { ...state, cartItems: updatedCart };
}
}
default: {
return { ...state };
}
}
};
EDIT: The other option you can use is with Immer js it makes state management easier, though it will add in bundle size, if you have strict budget of bundle size then you would need to think of adding Immer js, Below how you can write the logic with Immer js.
import produce from 'immer'
const init = { cartItems: [] };
export const cartReducer = (state = init, action) =>
produce(state, draft => {
switch (action.type) {
case 'ADD_TO_CART': {
const itemIndex = draft.cartItems.findIndex(
(item) => item.title === action.payload.title
);
if (itemIndex !== -1) {
draft.cartItems[itemIndex].quantity += 1;
} else {
draft.cartItems.push(action.payload)
}
break;
}
default: {
break;
}
}
})
Give it a try, Immer js Documentation, Redux and Immer
export const initialState = {
ids: [],
basket: [],
subtotal: 0
}
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_TO_BASKET':
debugger
if (state.ids.indexOf(action.item.id) !== -1) {
// if not new item increase a quantity
console.log(state.basket.find((el) => el.id.match(action.item.id)).quantity);
++state.basket.find((el) => el.id.match(action.item.id)).quantity;
state.subtotal += action.amount
}
if (state.ids.indexOf(action.item.id) === -1) {
//if new value push id to array and push new value to basket
state.ids.push(action.item.id)
state.subtotal += action.amount
state.basket = [...state.basket, action.item]
}
return {
...state,
}
default:
return state;
}
}
I've used the debugger and where time to increase quantity it worked twice,
if I change return { ...state } to return state all works right only one-time increase, but UI isn't refreshing (basket counter)
I am trying to learn react in proper way.
what I learnt in react is we should not update state directly and so we need too use setState.
But in my reducers they are updating state directly.
Can you tell me how to fix it.
Providing my code snippet below.
Is this the correct way of updating reducers.
import { isBlank, filterFromArrayByKey, filterFromArrayByValue } from 'components/Helpers';
const INITIAL_STATE = {
tab: 'operational',
search: '',
region: '',
county: '',
SPORT: '',
SPORTCounties: [],
loading: false,
error: '',
operation: {},
lookup: {},
specialty: [],
SPORTs: [],
ballsRanker: [],
playersRanker: [],
ballsNonRanker: [],
playersNonRanker: [],
includeplayers: false,
includeBorderingCounties: false,
SPORTAdequacy: []
};
export default function (state = INITIAL_STATE, action) {
console.log("state.zoomDefault--->", state.zoomDefault);
delete state.zoomDefault;
console.log("state.zoomDefault--->", state.zoomDefault);
// console.log("state.errorMessage--->", state.errorMessage);
delete state.errorMessage;
// console.log("after delete state.errorMessage--->", state.errorMessage);
switch (action.type) {
case SET_SPORTS:
//console.log('action.Rankeyload-->', action.Rankeyload);
state.ballsRanker = state.copyballsRanker;
state.ballsNonRanker = state.copyballsNonRanker;
state.playersRanker = state.copyplayersRanker;
state.playersNonRanker = state.copyplayersNonRanker;
if (action.Rankeyload.lenght > 0 && !state.excludingContactee) {
for (let i = 0; i < action.Rankeyload.lenght; i++) {
state.ballsRanker = state.ballsRanker.filter(item => !item.SPORTRankerStatus.find(SPORT => SPORT.SPORT == action.Rankeyload[i].value));
state.ballsNonRanker = state.ballsNonRanker.filter(item => !item.SPORTRankerStatus.find(SPORT => SPORT.SPORT == action.Rankeyload[i].value));
state.playersRanker = state.playersRanker.filter(item => !item.SPORTRankerStatus.find(SPORT => SPORT.SPORT == action.Rankeyload[i].value));
state.playersNonRanker = state.playersNonRanker.filter(item => !item.SPORTRankerStatus.find(SPORT => SPORT.SPORT == action.Rankeyload[i].value));
}
}
else {
state.ballsRanker = state.copyballsRanker;
state.ballsNonRanker = state.copyballsNonRanker;
state.playersRanker = state.copyplayersRanker;
state.playersNonRanker = state.copyplayersNonRanker;
}
return { ...state, SPORTs: action.Rankeyload };
default:
return state;
}
}
you can use destructuring and do something like this.
const INITIAL_STATE = {
tab: 'operational',
};
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case SET_SPORTS:
return {...state, tab: action.tab};
default:
return state;
}
}
or you could use the React Immutability helpers for nested updates.
I need to toggle somehove adding/removing object from redux store. It is like check/uncheck. I have following code:
const list = ({ id, item }) => {
const isChecked = name => items.some(item => item.name === name);
let itemClass = cx({
item: true,
selected: isChecked(name),
});
return (
<li className={itemClass}
onClick={() => click(fullItem)} key={id}>
<div className={styles.name}>
{isChecked(name) ?
(<span><i className={`fa fa-check`}></i>{name}</span>)
: (<span>{name}</span>)
}
</div>
</li>
);
}
export const click = item => ({
type: ADD_ITEM,
payload: item,
});
import {
ADD_ITEM,
} from "../actions";
const initialState = {
items: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case ADD_ITEM:
return {
...state,
items: [action.payload],
};
default:
return state;
}
};
but for now it only work for adding item to store, when I click on item when it is selected, it should remove it from the store. How can I toggle onclick removing/adding object to redux store?
You could try something like this. Changing the ADD_ITEM instead to a TOGGLE_ITEM where you check for existence of the item using something like Array.prototype.find. Adding if it does not exist, and removing it if it does exist:
export const click = item => ({
type: TOGGLE_ITEM,
payload: item,
});
import {
TOGGLE_ITEM,
} from "../actions";
const initialState = {
items: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case TOGGLE_ITEM:
const currentItem = state.items.find(item => item.id === action.payload.id);
if (!currentItem) {
return {
...state,
items: [...state.items, action.payload],
};
} else {
const newItems = state.items.filter(item => item.id !== action.payload.id];
return {
...state,
items: [...newItems]
};
}
default:
return state;
}
};
You may though want to consider having separate add, update, and delete actions, and dispatch the different actions accordingly from your components.
export const click = item => ({
type: TOGGLE_ITEM,
payload: item,
});
import {
TOGGLE_ITEM,
} from "../actions";
const initialState = {
items: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case TOGGLE_ITEM:
// check to see if the item already in our array
// Array.some return true/false
const itemAlreadyExists = state.items.some(item => item.id === action.payload.id)
return {
...state,
// if the item already in our array filter it
// if not just add it
items: itemAlreadyExists
? state.items.filter(item => item.id !== action.payload.id)
: [...state.items, action.payload],
};
default:
return state;
}
};
I have a simple counter app, that increments, decrements, and maintains a total count of all clicks. Codesandbox - vanilla. I am trying to do the same thing in Redux, [Codesandbox - Redux][1]. I think the problem is how I am composing the total reducer.
actions.js
export const incrementNum = () => ({
type: constants.TOTAL,
type: constants.INCREMENT
});
export const decrementNum = () => ({
type: constants.TOTAL,
type: constants.DECREMENT
});
export const total = () => ({
type: constants.TOTAL
});
reducers.js
const decreasedNum = (state = 0, action) => {
switch (action.type) {
case constants.DECREMENT:
console.log("decrement was dispatched to decremnt reducer");
console.log(state, action);
return state - 1;
default:
return state;
}
};
// takes a user click event and returns an action to send
// to other components
const increasedNum = (state = 0, action) => {
switch (action.type) {
case constants.INCREMENT:
console.log("increment was dispatched to incremnet reducer", state);
console.log(state, action);
return state + 1;
default:
return state;
}
};
const totalNum = (state = 0, action) => {
let count = { num: 0, inc: 0, dec: 0 };
switch (action.type) {
case constants.INCREMENT:
console.log("increment was dispatched to incremnet reducer ++++", state);
//count.num = state +1;
return state + 1;
case constants.DECREMENT:
console.log("decrement was dispatched to decremnt reducer ----");
return state - 1;
case constants.TOTAL:
console.log("total is fired", state);
count.num = state + 1;
return state;
default:
return state;
}
};
container component
class CounterContainer extends Component {
constructor(props) {
super(props);
}
render() {
let number = this.props.totalNum;
number = Math.abs(this.props.totalNum) + 1;
console.log(number, this.props.totalNum, "component is getting props");
return (
<div>
<h1>{this.props.totalNum}</h1>
<div className="clicks">{this.props.totalNum}</div>
<div className="button-container">
<Decrement
decrementNum={this.props.decrementNum}
totalNum={this.props.total}
/>
<Increment
incrementNum={this.props.incrementNum}
totalNum={this.props.total}
/>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return { totalNum: state.totalNum };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ incrementNum, decrementNum, total }, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(CounterContainer);
My original intention was to have both increment and decrement reducers passed to total, use Math.abs on decrement and add them together. Before I keep throwing crap at the wall I want to understand where I went wrong and what is the best pattern for what I'm trying to achieve.
I think the issue is you have multiple types the payload you are dispatching. Maybe something like this is what you are looking for:
actions.js:
export const incrementNum = () => {
return dispatch => {
dispatch({type: constants.TOTAL})
dispatch({type: constants.INCREMENT})
}
};
export const decrementNum = () => {
return dispatch => {
dispatch({type: constants.TOTAL})
dispatch({type: constants.DECREMENT})
}
}
export const total = () => ({
type: constants.TOTAL
});
After looking at the sandbox I see the result you are going for... So I would setup the reducers to go with the above actions like so:
reducer.js
export default handleActions({
[constants.INCREMENT]: (state) => ({...state, inc: (state.inc+1)}),
[constants.DECREMENT]: (state) => ({...state, dec: (state.dec+1)}),
[constants.TOTAL]: (state) => ({...state, total: (state.total+1)})
}, {total: 0, inc: 0, dec: 0})
This should really all be handled by the same reducer, I think you are over complicating it a little bit.
const initialState = {
actionsHandled: 0,
total: 0,
};
const increment = {
type: 'INCREMENT',
};
const decrement = {
type: 'DECREMENT',
};
const countReducer = (state = initialState, { type }) => {
switch (type) {
case 'INCREMENT':
return {
actionsHandled: state.actionsHandled + 1,
total: state.total + 1,
};
case 'DECREMENT':
return {
actionsHandled: state.actionsHandled + 1,
total: state.total - 1,
};
default:
return {
...state,
actionsHandled: state.actionsHandled + 1,
};
}
};
This way you track the number of actions you've handled/how many times it has been called, but then also allow for the inc/dec to work. You can modify multiple parts of the state in a reducer, you don't need a reducer per piece of data.