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
Related
I have an array of objects in a useState hook with which i rendered input elements.
The state doesn't get updated when the input element changes.
Here Is My Code.
ProceduralResponseAnswerCreate Component
const ProceduralResponseAnswerCreate = () => {
const [proceduralAnswers, setProceduralAnswers] = useState([{ "value": "One" }, { "value": "two" }])
return <>
<ol>
{proceduralAnswers.map((answer, answer_index) => <input key={answer_index} style={inputStyle} onChange={(event) => updateAnswerValue({ event, setProceduralAnswers, answer_index })} value={answer.value} />)}
</ol>
</>
}
export default ProceduralResponseAnswerCreate
updateAnswerValue function
export const updateAnswerValue = ({ event, setProceduralAnswers, answer_index }) => {
setProceduralAnswers(state => {
var newState = state;
var currentAnswer = newState.filter((state_1, index) => index === answer_index)[0]
currentAnswer.value = event.target.value
return newState
})
}
I think it doesn't work because the next state has the same Reference as the previous state, you only change the value of one element. The useState hook will trigger a rerender, if the reference changes. So your Callback must look like this.
setProceduralAnswers(state => {
let newState = state;
let currentAnswer = newState.filter((state_1, index) => index === answer_index)[0]
currentAnswer.value = event.target.value
return [...newState]
})
try this
export const updateAnswerValue = ({ event, setProceduralAnswers, answer_index }) => {
setProceduralAnswers(state => {
var newState = state;
newState[answer_index].value = event.target.value
return newState
})
}
You are returning the newState instead of the currentAnswer.
First off, if you're only trying to find one item use [].find not filter.
var newState = state; does not give you a fresh copy, change newState, and you'll be changing state too, you need to do var newState = [...state]; (warning: this is a shallow copy only).
It looks like you think currentAnswer shares a reference with newState in some way, which it doesn't. Whenever you run map, find, filter or reduce you're getting a fresh copy of the array so there will not be a link between the 2.
A good bet here is to just map the state
export const updateAnswerValue = ({
event,
setProceduralAnswers,
answer_index
}) => {
setProceduralAnswers((state) => state.map((state_1, index) => {
if (index === answer_index) {
return {
...state_1,
value: event.target.value
}
}
return state_1;
}));
};
I have a warning on this nested setState pointing a "bad setState call" because I am calling a setState inside a setState, so I understand I must avoid that and take out the children setState from there, the problem is that the children is using the parent prevState for a conditional so I don't know how to solve this.
This is my code:
const setStateImageAndIndex = (state, image) => {
setSketchState(prevState => {
if (state.index !== undefined && prevState.index !== state.index) {
setCurrentSketchIndex(state.index);
}
const new_state = {
...prevState,
...state,
image: image
};
return new_state;
});
};
The warning is pointing the setCurrentSketchIndex(state.index); explicitly, which is the function I want to take out from setSketchState. Thank you!
prevState in setSketchState infact is the actual state of sketchState (I mean the state setted by setSketchState). So you could write something like:
const setStateImageAndIndex = (state, image) => {
if (state.index !== undefined && sketchState.index !== state.index) {
setCurrentSketchIndex(state.index);
}
setSketchState(prevState => {
const new_state = {
...prevState,
...state,
image: image
};
return new_state;
});
};
you can hold the previous state of the parent component in a useRef and derivate the state in the child component passing that value down as a prop, for example:
function Parent() {
let [state, setState] = React.useState({ version: 1 });
let prevState = usePrevious(state.version);
return (
<div>
<p>Parent value: {state.version}</p>
<Child value={prevState} />
<button
onClick={() =>
setState({
version: state.version + 1
})
}
>
Update version
</button>
</div>
);
}
function Child(props) {
return (
<div>
<p>Child value: {props.value || "No value yet"}</p>
</div>
);
}
as we can see above, the parent component will update the state.version in the button click and usePrevious will hold the previous value:
function usePrevious(value) {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
});
return ref.current;
}
Working example in: https://codesandbox.io/s/use-previous-hook-0iemv
According to the comments above from #GabrielePetrioli and #GiovanniEsposito, this solution seemed to be safer.
const setStateImageAndIndex = (state, image) => {
let updateCurrentSketchIndex = false;
setSketchState(prevState => {
if (state.index !== undefined && prevState.index !== state.index) {
updateCurrentSketchIndex = true;
}
const new_state = {
...prevState,
...state,
image: image
};
return new_state;
});
if (updateCurrentSketchIndex) {
setCurrentSketchIndex(state.index);
}
};
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
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.