setState hook changing another state - javascript

I'm new to React. I'm having the next problem...
At my functional component I have many states, there are 2 that have the sames fields (one is for an auxiliary operation)
const [fieldsToEdit, setFieldsToEdit] = useState({}); // This one get populated after the first render
const [auxFields, setAuxFields] = useState({.....})
Now, I have a button that calls a function, this functions just edits the 'fieldsToEdit', but it is editing the auxFields too! I realized this writing console.logs after and before of the setState call.
const updateEditHandler = (event) => {
event.persist());
setFieldsToEdit((prevState) => {
const { name, value } = event.target;
if(name === "fecha_presentacion")
prevState[name] = value;
else
prevState[name] = Number(value);
return ({
...prevState
});
}
Am I doing it wrong? Hope you can help me.

You should not mutate state. Instead create a new object without modifying the previous one.
prevState[name] = value;
return { ...prevState };
The above first mutates the previous state, then returns a copy of it. Instead return a copy that contains the new value without modifying the previous state.
return { ...prevState, [name]: value };
The above copies the previous state and adds or overrides the (evaluated) name property with value. This is all done without mutating prevState.
Applying this to your actual code you would get the following.
setFieldsToEdit((prevState) => {
const { name, value } = event.target;
if (name == "fecha_presentacion") {
return { ...prevState, [name]: Number(value) };
} else {
return { ...prevState, [name]: value };
}
});
// or (depending on preference)
setFieldsToEdit((prevState) => {
let { name, value } = event.target;
if (name == "fecha_presentacion") value = Number(value);
return { ...prevState, [name]: value };
});

setFieldsToEdit((prevState) => {
const { name, value } = event.target;
if(name === "fecha_presentacion")
prevState[name] = value;
else
prevState[name] = Number(value);
return ({
...prevState
});
In prevState[name] = you mutating the state. You need to clone it and set it with either lodash deepClone or JSON.stringify and then JSON.parse if you are not familiar with lodash.
You are not sharing your code so useState({.....}) does not mean anything.
But from the general picture I am getting from your code I think that fieldsToEdit and auxFields have the same reference so because you prevState[name] = you are changing both
Try to not mutate your state in any framework

Related

How to change update an object within a useState array

Suppose we have an array of objects in userInformation:
[
{
firstName:'Bob',
lastName:'Dude',
},
{
firstName:'John',
lastName:'Rad',
}
]
const [userInformation, setUserInformation] = useState([]);
userInformation.forEach((user, index) => {
if (snapshot.val().leadID === user.leadID) {
setUserInformation((userInformation) => ({
...userInformation,
[index]: snapshot.val(),
}));
}
});
I would like to update the second object.
My code doesn't seem to be working quite right. Any suggestions?
Yes few suggestions I have for you:)
First of all you have never assigned userinformation to your state.
So it should be some what like below
const [userInformation, setUserInformation] = useState(userinformation);
Or you must be getting userinformation from an API and using useEffect to initialize it.
Second thing is you must have an id as well as index key on each user something like this:
[
{
leadId:1,
firstName:'Bob',
lastName:'Dude',
index:1
},
{
leadId:2,
firstName:'John',
lastName:'Rad',
index:2
}
]
Now, Coming to what you are expecting you can use map() function in such as way that once the condition is met, you update that particular user, otherwise you should return back same users when condition is not met.
const updatedUsers = userInformation.map((user, index) => {
if (snapshot.val().leadID === user.leadID) {
return setUserInformation((userInformation) => ({
...userInformation,
[index]: snapshot.val(),
}));
}
return userInformation;
});
Here I think a simple find() would do the trick, rather than trying to loop the array.
const updateInfo = (id, obj /* snapshot.val() */) => {
const item = userInformation.find(({ leadID }) => leadID === id);
const updatedItem = {
...item,
...obj
};
setUserInformation((previousInfo) => {
...userInformation,
...updatedItem
})
}
Sorry for the lack of information provided in my question.
I was using firebase realtime with React, and not wrapping my logic in a useEffect was one problem.
I went with this solution:
setUserInformation(
userInformation.map((user) =>
user.leadID === snapshot.val().leadID
? {
...user,
...snapshot.val(),
}
: user
)
);
Thank you for all your answers!

Cannot use previous value using useRef hook. previous value is showing equal to current value

i cannot use previous value using useRef hook. previous value is equal to previous value. can someone tell me why?? and what is the solution.
i need to compare previous value of post;
const [selectedPost, setSelectedPost] = useState({ title: "", content: "" });
Following is previous value function
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const prevVal = usePrevious(selectedPost);
Following is the function in which i use previous value
const handleClickUpdate = () => {
if (prevVal === selectedPost && isEmpty(errors))
return setEditClicked(false);
editPost(selectedPost, postId, "original", () => setEditClicked(false));
};
Edit//
i found out its happening because of this block of code. can someone explain why??
//clear validation errors on typing title or content
useEffect(() => {
if (!isEmpty(selectedPost.title) && !isEmpty(selectedPost.content))
dispatch(saveErrors({}));
}, [selectedPost, dispatch]);
It was happening because of this use Effect hook. maybe because selected post chang effect was already in use here so compiler wasnt able to execute the same effect to store previous value since this below code block came first. i removed it and instead restored errors in handle change. This solved my problem.
Previously:
useEffect(() => {
if (!isEmpty(selectedPost.title) && !isEmpty(selectedPost.content))
dispatch(saveErrors({}));
}, [selectedPost, dispatch]);
now:
const handleChange = (event) => {
const { name, value } = event.target;
// clear validation errors on typing title or content
if (!isEmpty(selectedPost.title) && !isEmpty(selectedPost.content))
dispatch(saveErrors({}));
setSelectedPost((prevVal) => {
return { ...prevVal, [name]: value };
});
};

Creating new array vs modifing the same array in react

Following is the piece of code which is working fine, but I have one doubt regarding - const _detail = detail; code inside a map method. Here you can see that I am iterating over an array and modifying the object and then setting it to setState().
Code Block -
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
const { invoiceData } = this.state;
invoiceData.map(invoiceItem => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(detail => {
const _detail = detail;
if (_detail.tagNumber === data.tagNumber) {
_detail.id = data.id;
}
return _detail;
});
}
return invoiceItem;
});
state.invoiceData = invoiceData;
}
this.setState(state);
};
Is this approach ok in React world or I should do something like -
const modifiedInvoiceData = invoiceData.map(invoiceItem => {
......
code
......
})
this.setState({invoiceData: modifiedInvoiceData});
What is the pros and cons of each and which scenario do I need to keep in mind while taking either of one approach ?
You cannot mutate state, instead you can do something like this:
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
this.setState({
invoiceData: this.state.invoiceData.map(
(invoiceItem) => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(
(detail) =>
detail.tagNumber === data.tagNumber
? { ...detail, id: data.id } //copy detail and set id on copy
: detail //no change, return detail
);
}
return invoiceItem;
}
),
});
}
};
Perhaps try something like this:
checkInvoiceData = (isUploaded, data) => {
// Return early
if (!isUploaded) return
const { invoiceData } = this.state;
const updatedInvoices = invoiceData.map(invoiceItem => {
if (invoiceItem.number !== data.savedNumber) return invoiceItem
const details = invoiceItem.details.map(detail => {
if (detail.tagNumber !== data.tagNumber) return detail
return { ...detail, id: data.id };
});
return { ...invoiceItem, details };
});
this.setState({ invoiceData: updatedInvoices });
};
First, I would suggest returning early rather than nesting conditionals.
Second, make sure you're not mutating state directly (eg no this.state = state).
Third, pass the part of state you want to mutate, not the whole state object, to setState.
Fourth, return a new instance of the object so the object reference updates so React can detect the change of values.
I'm not saying this is the best way to do what you want, but it should point you in a better direction.

Which approach in React is better?

Below both code does exactly same but in different way. There is an onChange event listener on an input component. In first approach I am shallow cloning the items from state then doing changes over it and once changes are done I am updating the items with clonedItems with changed property.
In second approach I didn't cloned and simply did changes on state items and then updated the state accordingly. Since directly (without setState) changing property of state doesn't call updating lifecycles in react, I feel second way is better as I am saving some overhead on cloning.
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const clonedItems = Array.from(items);
clonedItems.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items: clonedItems });
};
OR
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items });
};
You can use this
this.setState(state => {
const list = state.list.map(item => item + 1);
return {
list,
};
});
if you need more info about using arrays on states, please read this: How to manage React State with Arrays
Modifying the input is generally a bad practice, however cloning in the first example is a bit of an overkill. You don't really need to clone the array to achieve immutability, how about something like that:
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const processedItems = items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
return {
...ele,
rate: Number(value)
};
} else {
return ele;
}
});
this.setState({ items: processedItems });
};
It can be refactored of course, I left it like this to better illustrate the idea. Which is, instead of cloning the items before mapping, or modifying its content, you can return a new object from the map's callback and assign the result to a new variable.

Reducer cannot read property 'photos' of undefined? What am I doing wrong?

Here is the initial state of my reducer, and I need to set it up in this way due to some post processing I need to do:
const initialState = {
showAll: {
photos: null
}
}
Basically, I have a page where you see all your photos, and you can tag certain ones as your pinned photos.
Here's part of my reducer logic:
if (state.showAll.photos) {
const showAllState = state.showAll.photos;
showAllState.map(m => {
if (action.payload.id === m.id) {
m.pinned = true;
}
});
showAllAfterPin = showAllState;
} else {
showAllAfterPin = state.showAll.photos;
}
However, I get an error saying cannot read property 'photos' of undefined and I'm not sure what I am doing wrong.
Might be easier to just set your photos in initialState to empty array [] instead of null.
Another thing, your reducer should not mutate your state object.
Doing const showAllState = state.showAll.photos doesn't make it a new object.
Last thing, showAllState.map(...) needs to return an item inside the function body. It will create a new array.
Here's something you can do...
const { photos = [] } = state.showAll;
const updatedPhotos = photos.map(photo => {
if (action.payload.id === photo.id) {
return Object.assign({}, photo, { pinned: true })
}
return photo;
});
// return entire state if this is inside your root reducer
return {
...state,
showAll {
...state.showAll,
photos: updatedPhotos
}
}

Categories

Resources