How do I update 2 objects with React Hook useState - reactjs

My first Object
const [message, setMessage] = useState({
Features: {Active:false},
General: {FileTimeout:0,ZipDepth:0}
});
How do I update this object?
const handleInput=e=>{
const name=e.currentTarget.name;
const value=e.currentTarget.value;
var temp = {...message}
if(name == 'active'){
if(value==='on'){
temp.Features.Active=true;
}
else{}
}
else if(name == 'timeout'){
temp.General.ZipDepth= 5;
}
else if(name == 'zipdepth'){
temp.General.FileTimeout= 7;
}
}
New Values= { Features :{Active:true}, General: {FileTimeout:7,ZipDepth:5}});
How can I update the values like this? If there is a library or something for this, I can also use it.

You are mutating your state object. Even though you create a copy temp of the message state, you are mutating the nested properties. You necessarily need to also shallow copy all nested state you are updating.
I would suggest using a functional state update to access the previous messagestate, and use aswitchstatement to cover the different cases on thename` value, returning the next state value for each one. Notice that each level of nested state is shallow copied before it's updated.
const [message, setMessage] = useState({
Features: { Active: false },
General: { FileTimeout: 0, ZipDepth: 0 }
});
const handleInput=e=>{
const { name, value } = e.currentTarget;
setMessage(message => {
switch(name) {
case 'active':
if (value === 'on') {
return {
...message, // shallow copy of state
Features: {
...message.Features, // shallow copy nested state
Active: true,
},
};
} else {
// ? return some new state
}
case 'timeout':
return {
...message, // shallow copy of state
General: {
...message.General, // shallow copy nested state
ZipDepth: 5,
},
};
case 'zipdepth':
return {
...message, // shallow copy of state
General: {
...message.General, // shallow copy nested state
FileTimeout: 7,
},
};
default:
return message; // no update, return current state
};
});
}

const [message, setMessage] = useState({
Features: {Active:false},
General: {FileTimeout:0,ZipDepth:0}
});
const handleInput=e=>{
const name=e.currentTarget.name;
const value=e.currentTarget.value;
var temp = {...message}
if(name == 'active'){
if(value==='on'){
temp.Features.Active=true;
}
else{}
}
else if(name == 'timeout'){
temp.General.ZipDepth= 5;
}
else if(name == 'zipdepth'){
temp.General.FileTimeout= 7;
}
setMessage({...temp}) // You need to call setMessage function in order to update the state.
}

Related

Use state not updating inside switch that is constantly updating

I have this program that every time the cart changes it runs. It checks if specific id is in the cart (everything until now is working as intended, if i console log "state" after the for loop it will correctly return the state). Then, I have an if statement that depending on the state value it will set a value on local storage and update setValuePixel.
In both cases, state true or false, the setValuePixel will not update the valuePixel value. The console.log after the set state return false either way.
I'm not sure why this is happening so any help is appreciated. Thanks
const RemoveItemsOnDeleteKit = ({children}:any) => {
const [valuePixel, setValuePixel] = useState<string | null>('false')
const [val, setVal] = useState<string | null>('')
const handleEvents = (e: PixelMessage) => {
switch (e.data.eventName) {
case 'vtex:cartChanged': {
const { items } = e.data
let state = false
if (items != undefined || items != null) {
for (let i = 0; i <= items.length; i++) {
// console.log(items[i])
if (items[i]?.skuId == "38"){
state = true
}
}
if (state == true) {
localStorage.setItem('isKitOnCart', 'true')
setValuePixel('true')
console.log('the set value pixel value is ', valuePixel)
}
else if(state == false) {
localStorage.setItem('isKitOnCart', 'false')
setValuePixel('false')
console.log("THE SET VALUE PIXEL VALUE IS ", valuePixel)
}
else {
console.log("THE ELSE STATEMETN IS HERE!")
}
}
}
default: {
break
}
}
}
}

Is it valid, to update the state by mapping to new Objects

Let's take this Update State example:
const initialState = [
{id: 1, country: 'Austria'},
{id: 2, country: 'Belgium'},
{id: 3, country: 'Canada'},
];
const [data, setData] = useState(initialState);
const updateState = () => {
setData(prevState => {
const newState = prevState.map(obj => {
if (obj.id === 2) {
return {...obj, country: 'Denmark'};
}
return obj;
});
return newState;
});
};
1. Is it also valid to update the state like this? (First example)
const updateState = () => {
const newState = data.map(obj => {
if (obj.id === 2) {
return {...obj, country: 'Denmark'};
}
return obj;
});
setData(newState);
};
2. Is it also valid to update the state like this? (Second example)
const updateState = () => {
setData(prevState => {
const newState = prevState.map(obj => {
if (obj.id === 2) {
let newObj = obj;
newObj.country = 'Denmark'
return newObj;
}
return obj;
});
return newState;
});
};
3. Do this specific versions also have performance impacts? Which one is the best?
The first and the second example are perfectly valid. I would, however, suggest you to use the first one and I will explain why:
With the first example you are using a callback as an argument of the function. And this form means that you are actually getting the last data state value (this is important because the state updates happen asynchronously). Whenever you need to update the state based on the previous value even React suggests to use the callback form to avoid side effects.
More infos here: https://reactjs.org/docs/hooks-reference.html#functional-updates
The third example is not valid because you are mutating directly the state. Something that in react is not allowed.
More infos here: https://dev.to/il3ven/common-error-accidentally-mutating-state-in-react-4ndg

MutationObserver is reading old React State

I'm attempting to use a MutationObserver with the Zoom Web SDK to watch for changes in who the active speaker is. I declare a state variable using useState called participants which is meant to hold the information about each participant in the Zoom call.
My MutationObserver only seems to be reading the initial value of participants, leading me to believe the variable is bound/evaluated rather than read dynamically. Is there a way to use MutationObserver with React useState such that the MutationCallback reads state that is dynamically updating?
const [participants, setParticipants] = useState({});
...
const onSpeechMutation = (mutations) => {
mutations.forEach((mutation) => {
// identify name of speaker
if(name in participants) {
// do something
} else {
setParticipants({
...participants,
[name] : initializeParticipant(name)
})
}
})
}
...
useEffect(() => {
if(!speechObserverOn) {
setSpeechObserverOn(true);
const speechObserver = new MutationObserver(onSpeechMutation);
const speechConfig = {
attributes: true,
attributeOldValue: true,
attributeFilter: ['class'],
subtree: true,
}
const participantsList = document.querySelector('.participants-selector');
if(participantsList) {
speechObserver.observe(participantsList, speechConfig);
}
}
}, [speechObserverOn])
If you are dealing with stale state enclosures in callbacks then generally the solution is to use functional state updates so you are updating from the previous state and not what is closed over in any callback scope.
const onSpeechMutation = (mutations) => {
mutations.forEach((mutation) => {
// identify name of speaker
if (name in participants) {
// do something
} else {
setParticipants(participants => ({
...participants, // <-- copy previous state
[name]: initializeParticipant(name)
}));
}
})
};
Also, ensure to include a dependency array for the useEffect hook unless you really want the effect to trigger upon each and every render cycle. I am guessing you don't want more than one MutationObserver at-a-time.
useEffect(() => {
if(!speechObserverOn) {
setSpeechObserverOn(true);
const speechObserver = new MutationObserver(onSpeechMutation);
const speechConfig = {
attributes: true,
attributeOldValue: true,
attributeFilter: ['class'],
subtree: true,
}
const participantsList = document.querySelector('.participants-selector');
if(participantsList) {
speechObserver.observe(participantsList, speechConfig);
}
}
}, []); // <-- empty dependency array to run once on component mount
Update
The issue is that if (name in participants) always returns false
because participants is stale
For this a good trick is to use a React ref to cache a copy of the current state value so any callbacks can access the state value via the ref.
Example:
const [participants, setParticipants] = useState([.....]);
const participantsRef = useRef(participants);
useEffect(() => {
participantsRef.current = participants;
}, [participants]);
...
const onSpeechMutation = (mutations) => {
mutations.forEach((mutation) => {
// identify name of speaker
if (name in participantsRef.current) {
// do something
} else {
setParticipants(participants => ({
...participants,
[name]: initializeParticipant(name)
}));
}
})
};

Initializing useState by modifying (mapping through) values of redux state

I'm trying to initialize my state with the redux state that I have stored. However, when I try to map through one of the lists stored in the state it just resets the values that I have inside the list instead of returning their substring values (which I want). The thing is if I print mail.substring(0, mail.length -10) I see the value that I would like to assign to the variable but after assigning the value is empty.
Here comes the strange part: if I were to assign "hello" instead of mail.substring(0, mail.length-10) then it works which could make you assume that the substring would return an empty value but as I mentioned above it does not.
I guess this might be because I create a shallow copy of the redux state but I'm not sure. Could you help me resolve this, please?
const membershipData = useSelector(getCompanyMembershipDetails);
function getInitState() {
if (membershipData !== null && membershipData !== undefined) {
const newState = { ...membershipData };
newState.members.map((m) => {
const mail = m.contact.countersignEmail;
const newVal = mail.substring(0, mail.length - 10);
m.contact.countersignEmail = newVal;
return m;
});
return newState;
} else
return {
members: [getEmptyMemberStateForId(0), getEmptyMemberStateForId(1)],
membershipRates: [
getEmptyPropertyContributionForId(0),
getEmptyPropertyContributionForId(1),
],
registrationPermissions: [],
};
}
const [membersData, setMembersData] = useState(getInitState());
It was because of the shallow copy as I thought. Using cloneDeep from lodash I made a working version:
const membershipData = useSelector(getCompanyMembershipDetails);
function getInitState() {
if (membershipData !== null && membershipData !== undefined) {
const newState = _.cloneDeep(membershipData);
newState.members.map((m) => {
const mail = m.contact.countersignEmail;
const newVal = mail.substring(0, mail.length - 10);
m.contact.countersignEmail = newVal;
return m;
});
return newState;
} else
return {
members: [getEmptyMemberStateForId(0), getEmptyMemberStateForId(1)],
membershipRates: [
getEmptyPropertyContributionForId(0),
getEmptyPropertyContributionForId(1),
],
registrationPermissions: [],
};
}
const [membersData, setMembersData] = useState(getInitState());
If you use lodash the way I did above make sure to import it the following way:
import _ from "lodash";

Updating one State variable changing the data in another state variable

I'm setting the data from api in two state variables both values are same.But when I am updating one state variable the another state variable also changing.
Api.get("url")
.then((response) => {
this.setState(
{
deliveryInfoSection2: Object.assign({}, response.data),
deliveryInfoSection2copy: Object.assign({}, response.data),
}
);
})
updateState() {
try {
newVal = { ...this.state.deliveryInfoSection2 };
newVal.orderDetails[index].bo = value.replace(global.REG_NUMBER_ONLY, '');
//After this state variable deliveryInfoSection2copy is also updating.
this.setState({ deliveryInfoSection2: newVal }, () => {
if (this.state.deliveryInfoSection2.orderDetails[index].bo != '') {
}
catch (e) {
alert("error" + e)
}
}
This is a issue with respect to shallow copy of variables while using spread operator in javascript. It has nothing to do with react's setState. The spread operator creates a shallow copy for the object.
response = {
orderDetails: [
{
bo: "tempData1"
},
{
bo: "tempData2"
}
]
}
deliveryInfoSection2 = Object.assign({}, response)
deliveryInfoSection2Copy = Object.assign({}, response)
//Here spread operator will create shallow copy and so, the references are copied and hence any update to one will update other.
newVar = { ...deliveryInfoSection2 }
newVar.orderDetails[0].bo = "newValue"
deliveryInfoSection2 = newVar
console.log("deliveryInfoSection2", deliveryInfoSection2)
console.log("deliveryInfoSection2Copy", deliveryInfoSection2Copy)
To fix this, you need to create a deep copy of your object.
You can use JSON.parse(JSON.stringify(object)) for the same.
response = {
orderDetails: [
{
bo: "tempData1"
},
{
bo: "tempData2"
}
]
}
deliveryInfoSection2 = Object.assign({}, response)
deliveryInfoSection2Copy = Object.assign({}, response)
//This will create a deep copy for the variable
newVar = JSON.parse(JSON.stringify(deliveryInfoSection2))
newVar.orderDetails[0].bo = "newValue"
deliveryInfoSection2 = newVar
console.log("deliveryInfoSection2", deliveryInfoSection2)
console.log("deliveryInfoSection2Copy", deliveryInfoSection2Copy)
Hope it helps. Revert for any doubts/confusions.

Resources