reducer:
function reducer(state, action) {
switch (action.type) {
case 'addAnotherBox':
return ????state[action.index] = [...state[action.index] + object;
default:
throw new Error();
}
}
hook
const [listState, listDispatch] = useReducer(reducer, list)
initial states/ variables
let object = {
comment:""
}
let box = [object]
let list = [box, []]
function:
const AddBox = (e, index) => {
listDispatch({type:"addAnotherBox", index})
}
button:
{ listState.map((data, index)=> (
<Button variant="outlined" onClick={(e) => AddBox(e, index)}>
Add box </Button> ))}
My problem is that the return.
I have no idea what to put there, i am inexperienced with useReducer and the mutation of arrays in state seems to be worse.
I know if it was just adding something to an array i could go:
[...state, object]
but as it is so heavily nested how would you change this?
I don't think you can do it in one line, but you can do it step by step.
case 'addAnotherBox':
let stateCopy = [...state]; //copy the actual state
stateCopy[action.index] = state[action.index] ? [...state[action.index], object] : [object]; //if state[action.index] already exists it adds your object, if it does not exist yet it creates a new array containing your object
return stateCopy; // return stateCopy as the new state
Related
I am new to using "#reduxjs/toolkit" (version "^1.5.1").
I am trying to remove an object from within the state's array (roundScore). This is usually something that is very simple to do using filter(). For some reason this isn't working and I can't figure out why. Here's my code:
Reducer slice:
import { createSlice } from "#reduxjs/toolkit";
export const roundScoreSlice = createSlice({
name: "roundScore",
initialState: {
roundScore: [],
},
reducers: {
deleteArrow: (state, action) => {
console.log(`action.payload = ${action.payload}`); // returns correct id
state.roundScore.filter((arrow) => arrow.id !== action.payload);
},
},
});
export const { deleteArrow } = roundScoreSlice.actions;
export default roundScoreSlice.reducer;
React component:
import React from "react";
import styled from "styled-components";
import { motion } from "framer-motion";
import { useDispatch } from "react-redux";
import { deleteArrow } from "../../redux-reducers/trackSession/roundScoreSlice";
export default function InputtedScore({
arrowScore,
id,
initial,
animate,
variants,
}) {
const dispatch = useDispatch();
const applyStyling = () => {
switch (arrowScore) {
case 0:
return "miss";
case 1:
return "white";
case 2:
return "white";
case 3:
return "black";
case 4:
return "black";
case 5:
return "blue";
case 6:
return "blue";
case 7:
return "red";
case 8:
return "red";
case 9:
return "gold";
case 10:
return "gold";
default:
return null;
}
};
return (
<ParentStyled
id={id}
initial={initial}
animate={animate}
variants={variants}
onClick={() => dispatch(deleteArrow(id))}
>
<Circle className={applyStyling()}>
{arrowScore}
<IconStyled>
<IoIosClose />
</IconStyled>
<IoIosClose className="redCross" />
</Circle>
</ParentStyled>
);
}
The state after adding 2 arrows would look like this:
roundScore: [
{
id:"e0f225ba-19c2-4fd4-b2bf-1e0aef6ab4e0"
arrowScore:7
},
{
id:"2218385f-b37a-4f2c-a8db-4e7e65846171"
arrowScore:5
}
]
I've tried a combination of things.
Using e.target.id within dispatch
Using e.currentTarget.id within dispatch
Using ({id}) instead of just (id) within dispatch
Wrapping the reducer function with or without braces e.g. within (state, action) => { /* code */ }
What is it I'm missing? I know this is going to be a simple fix but for some reason it's eluding me.
Any help is much appreciated.
Okay, it looks like the issue is in the way how filter method works, it returns a new array, and an initial array is not mutated (that's why we have been using filter before to manipulate redux state), also in the code you've shown value of the filtering operation not assigned to any property of your state
You need to assign the value or mutate array, so the code below should work for you
state.roundScore = state.roundScore.filter((arrow) => arrow.id !== action.payload);
Mutate your existing array:
state.roundScore.splice(state.roundScore.findIndex((arrow) => arrow.id === action.payload), 1);
We can think outside of the box and look at it from another way. the React component above is just actually a child of a certain parent component. And for the purpose of my answer i assume that in your parent component you have some form of array.map .
So from that code, each array item will already have an array index. and you can pass that index as a prop to the above react component like so:
const InputtedScore = ({ ...all your props, id, inputIndex }) => {
const dispatch = useDispatch();
// Having access to your index from the props, you can even
// find the item corresponding to that index
const inputAtIndex_ = useSelector(state => state.input[inputIndex])
const applyStyling = () => {
switch (arrowScore) {
...your style logic
}
};
return (
// you can send that index as a payload to the reducer function
<ParentStyled id={id} onClick={() => dispatch(deleteArrow(inputIndex))}
...the rest of your properties >
<Circle className={applyStyling()}>
{arrowScore}
<IconStyled>
<IoIosClose />
</IconStyled>
<IoIosClose className="redCross" />
</Circle>
</ParentStyled>
);
}
After dispaching the delete action by sending as payload the item's index already, you do not need to find the item in the reducer anymore:
deleteMeal: (state, action) => {
// you receive you inputIndex from the payload
let { inputIndex } = action.payload;
// and you use it to splice the desired item off the array
state.meals.splice(inputIndex, 1);
...your other logic if any
},
You need to mutate your existing array
state.roundScore.splice(state.roundScore.findIndex((arrow) => arrow.id === action.payload), 1);
when running my reducer dispatch method here:
function reducer(state, action) {
switch (action.type) {
case "updateBox":
return state.map((input, index) => {
input.map((data, NextIndex)=>{
if (action.NextIndex === NextIndex) { data.comment = action.text } else {data.comment = data.comment}
})
});
the deve tools shows that regardless of if the action.Nextindex = NextIndex the data.comment will always be changed to the action.text.
other code:
let object = {
comment:""
}
let box = [object, object]
let list = [box, box]
const [listState, listDispatch] = useReducer(reducer, list);
{ listState.map((data, index)=> (
{data.map((input, NextIndex)=>(
<TextField value={data[NextIndex].comment}
onChange={(e)=>{listDispatch({type:"updateBox", text:e.target.value, field:"comment", NextIndex, index})}}
multiline fullWidth variant="outlined"/>
)})}
sandbox link
https://codesandbox.io/s/charming-sid-wdn0h?file=/src/App.js
Just because the variable is not in the React state does not mean it is copy by value.
When each variable is changed then the other variables are also changed.
better would also be to replace the mapped aspect as this:
let stateCopyr = [...listState];
stateCopyr[action.index][action.NextIndex].comment = action.text
console.log(stateCopyr)
console.log("update")
return stateCopyr
So I have an app that, whenever I push a button it adds objects to the redux store, the thing is that whenever I press that button the store doesn't really hold the items that I've added but it actually just replaces them, how can I make it so that when I press the button it saves the object inside of it, then on the second click it adds the next object and so on.
Here's my code:
That's where my button is:
{item.map(product => (
<div>
<h1>{product.name}</h1>
<h2>{product.Price}</h2>
<button onClick={() => buyProduct(product)}>
Buy
</button>
</div>
)}
That's where my buyProduct function is:
const buyProduct = (item) => {
store.dispatch({type:"buy", payload: item})
}
And that is all of my redux code:
const reducer = (state, action) => {
if (action.type === 'buy') {
return action.payload;
} else if (action.type === 'Decrease') {
return action.payload
}
return state;
}
const store = createStore(reducer, 0)
store.subscribe(()=>{
console.log('Store is now: ', store.getState())
})
const buyProduct = (item) => {
store.dispatch({type:"buy", payload: item})
}
How can I also make it so that I could loop over the store's stored objects
in the reducer you should add the new data to the current data. if your cart-data is an array of objects, you should push the new product object to the array. for example :
if(action.type==='buy')
{
return {...state, cartArray: [...state.cartArray, action.payload] };
}
and the same logic in the Decrease action.
I have a modal component in my React Native mobile app. It receives an array of objects from Redux state. I can delete a specific item in the array using dispatching an action using useDispatch hook. However, after sending the delete action, the component state is not updated automatically, so that I have to reopen the modal every time to see the updated list.
How can I set the modal to automatically re-render when the redux state is changed using dispatch?
SelectedItems.js
const SelectedItems = () => {
const vegetables = useSelector(state => state.new_order.vegetables)
return (
<Modal visible={isVisible}>
{vegetables.map( (v,index) =>
<VegeItem
key={index}
index={index}
name={v.name}
qty={v.qty}
metric={v.metric}
removeItem={(index) => {
dispatch({
type: 'DELETE_VEGE',
id: index
})
}}
/>)}
</View>
</Modal>
)
}
newOrderReducer.js
const newOrderReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'ADD_VEGE':
let updatedList = [...state.vegetables,action.vege]
return {
...state,
vegetables: updatedList
}
case 'DELETE_VEGE':
let newVegeList = state.vegetables
newVegeList.splice(action.id,1)
return {
...state,
vegetables: newVegeList
}
default:
return state
}
};
while doing like so let newVegeList = state.vegetables, newVegeList is just a pointer on your state and not a shallow copy of it. Therefore, you still can't mutate it as you can't mutate state outside the return part of the reducer.
so you can do like let newVegeList = [...state.vegetables], or directly at the return
return {
...state,
vegetables: state.vegetables.filter((veg, i) => i != action.id)
}
you can also send veg name or whatever and modify the checker at filter
I have stored an array of object in Redux State and inside each object there is a key named price. Now when I increment the quantity button I need to access the object that has the key inside redux and change the price value. I was able to do that but it's not working properly the price is being changed but a new object is being added in the state of Redux you can see it in the screenshot below. hope I was able to explain the problem clearly. if not please let know so I can explain more.
Cart Component
increment(e, item){
let qty = e.target.previousElementSibling.textContent;
qty++;
e.target.previousElementSibling.textContent = qty;
this.props.changePrice(item);
}
<div>
<input onClick={(e) =>this.decrement(e)} type="submit" value="-"/>
<span>1</span>
<input onClick={(e) => this.increment(e, item)} type="submit" value="+"/>
</div>
function mapStateToProps(state){
return({
itemlist: state.rootReducer
})
}
function mapDispatchToProps(dispatch) {
return({
removeItem: (item)=>{
dispatch({type: 'removeCart', payload: item})
},
changePrice: (item)=>{
dispatch({type: 'changePrice', payload: item})
}
})
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart);
Reducer Component
const changePrice = (itemArray, item)=>{
let newObject = {};
let filteredObject = itemArray.filter(reduxItem => reduxItem.id === item.id);
let newprice = filteredObject[0].price + filteredObject[0].price;
filteredObject[0].price = newprice;
newObject = filteredObject;
const something = ([...itemArray, newObject]);
return something;
}
const reducer = (state = [], action) =>{
switch (action.type) {
case 'Add':
return [...state, action.payload]
case 'removeCart':
const targetItemIndex = state.indexOf(action.payload);
return state.filter((item, index) => index !== targetItemIndex)
case 'changePrice':
return changePrice(state, action.payload)
default:
return state;
}
}
export default reducer;
filteredObject is an array. You override the newObject to be an array in this statement newObject = filteredObject. So the newObject is an array ( in [...itemArray, newObject] ) rather than an object. Keep things simple without unnecessary complexity.You can use Array.map. So do this instead
const changePrice = (itemArray, item) => {
return itemArray.map(reduxItem => {
if(reduxItem.id === item.id){
reduxItem.price = reduxItem.price + reduxItem.price
}
return reduxItem
});
};
See this for more info https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#inserting-and-removing-items-in-arrays
Hope this helps!
Instead of mutating the state.
// use this
const newState = Object.assign({},state);
We can create a new state and now if you do this, this works fine.
This avoids mutating state.