setState not maintaining set value after value set with Callback - reactjs

I'm working on an application where I multiple some values and use the sum on a computation a bit further. I quickly realized that this.setState wasnt working and I saw a callback being said to use a callback so I used this.setState({ estAlumTxt: estLBCarbonAl }, () => { however something strange is still happening. If I display the estAlumTxt variable in the console it's correct but if I display it lower down, it no longer has it's value set. This is my code.
calWasteCarbon = (event) => {
if (`${this.state.reAlumDrop}` === '1') {
const estLBCarbonAl = (1 * -89.38);
//this.setState({ estAlumTxt: estLBCarbonAl }
this.setState({ estAlumTxt: estLBCarbonAl }, () => {
console.log(this.state.estAlumTxt) // Returns the correct value set
}) } else {
this.setState({ estAlumTxt: 0 })
}
if (`${this.state.recPlasDrop}` === '1') {
const estLBCarbonPlas = (1 * -35.56);
this.setState({ estPlasTxt: estLBCarbonPlas })
} else {
this.setState({ estPlasTxt: 0 })
}
alert(this.state.estAlumTxt); //Alerted value is wrong.
Even if console.log reads out estAlumTxt correctly, the alert has it wrong. Gr

Calls to this.setState are asynchronous, meaning that they do not occur synchronously at the time they are called, but may occur at a later time.
This means, that while your function is executing, state.estAlumTxt hasn't been updated yet when you call the alert function.
Let's refactor your function a bit so that we only need to call setState once.
First lets extract those number consts out of the function, and I would advise putting them outside the component. There is no need to recalculate those consts every time the function is called.
Then lets create a stateChange variable where we can add the properties that we want to conditionally change. After we check the conditions, lets call setState, and alert the value of the stateChange.estAlumTxt
const estLBCarbonAl = 1 * -89.38;
const estLBCarbonPlas = 1 * -35.56;
calWasteCarbon = (event) => {
const stateChange = {};
if (`${this.state.reAlumDrop}` === '1') {
stateChange.estAlumTxt = estLBCarbonAl;
} else {
stateChange.estAlumTxt = 0;
}
if (`${this.state.recPlasDrop}` === '1') {
stateChange.estPlasTxt = estLBCarbonPlas;
} else {
stateChange.estPlasTxt = 0;
}
this.setState(stateChange);
alert(stateChange.estAlumTxt);
};

Related

Hooks (useLayoutEffekt) gets triggered too often / values inside Component

I am having problems figuring out the correct way how to use useEffect / useLayoutEffect.
I have a component, which waits on a pointerevent (PointerEventTypes.POINTERTAP (comes from babylon). It is within a useLayoutEffect hook. It then fires up pointerClick() to do something.
The problem I am having is that there are 2 dependencies I need to watch: buildings and activeBuilding. How can I achieve that the function pointerClick() does not get triggered two times on the first single click?
I think what is happening is that there is one event (PointerTAP) and the function gets called. Then within the function I am changing activeBuilding which then again triggers a re-render and a second run of the function pointerClick().
If I instead use useEffect (not useLayoutEffect) I have to click twice to set the data because it does change the values after rendering. So it's not what I want.
Here is the example code:
const pointerClick = () => {
if (scene) {
const pickInfo = scene.pick(scene.pointerX, scene.pointerY)
if (buildings.length === 0 && activeBuilding === -1) {
setActiveBuilding(0)
}
if (activeBuilding != -1) {
if (pickInfo?.pickedMesh?.metadata?.measurement?.type === 'handle') {
handleDeletePoint(
pickInfo.pickedMesh.metadata.measurement.id,
activeBuilding
)
} else if (pickInfo?.pickedMesh?.name === 'measureOutline') {
setActiveBuilding(-1)
}
else if (
!pickInfo?.pickedMesh?.metadata?.measurement &&
pickInfo?.pickedPoint
) {
handleAddPoint(pickInfo.pickedPoint, activeBuilding)
}
}
}
}
useLayoutEffect(() => {
if (scene) {
const obs = scene?.onPointerObservable.add((pointerInfoEvent) => {
switch (pointerInfoEvent.type) {
case PointerEventTypes.POINTERTAP:
pointerClick()
// setClicked(false)
break
}
})
return () => {
scene.onPointerObservable.remove(obs)
}
}
}, [canvas, scene, buildings, activeBuilding])
return <Measure />
}
Do I have to get rid of one of the two: buildings or activeBuilding as a dependency (what I think I cannot do since I need both values to be changeable). What am I doing wrong here?
Thank you so much!

State value not updating in react

I'm working on my first React application and I'm not understanding why the State doesn't have the updated value.
Here is my code:
const SlideOutPanel = forwardRef((props: any, ref: any) => {
const initCss: string = 'is-slide-out-panel';
const [compClass, setCompClass] = useState(initCss);
useImperativeHandle(ref, () => ({
open() {
open();
},
close() {
close();
},
}));
function refresh(): any {
let classVal: string = compClass;
if (props.direction === 'left-to-right') {
classVal = `${classVal} left-to-right`;
} else if (props.direction === 'right-to-left') {
classVal = `${classVal} right-to-left`;
}
if (Types().boolVal(props.userOverlay)) {
classVal = `${classVal} use-overlay`;
}
if (Types().boolVal(props.pushMain)) {
classVal = `${classVal} push-effect`;
}
if (props.theme === 'dark') {
classVal = `${classVal} theme-dark`;
}
setCompClass(classVal);
let classValdd: string = compClass;
}
function open(): void {
let classVal: string = compClass;
}
useEffect(() => {
refresh();
}, []);
return (
<section id={id} className={compClass}>
<div className="content">{props.children}</div>
</section>
);
});
I call refresh() when the components first load, which basically sets the className based on the passed props. At the end of the function, I set state "setCompClass" the value of "classVal" which works as I verified in Chrome Debugger. But on the same function I have the following line "let classValdd: string = compClass;" just to check what the value of "compClass" is and its always "is-slide-out-panel".
At first I thought it has to do with a delay. So when I call open() to do the same check, the value is still "is-slide-out-panel". So I'm a bit confused. Am I not able to read the state value "compClass"? Or am I misunderstanding its usage?
Setting the state in React acts like an async function.
Meaning that when you set it, it most likely won't finish updating until the next line of code runs.
So doing this will not work -
setCompClass(classVal);
let classValdd: string = compClass;
You will likely still end up with the previous value of the state.
I'm not exactly sure what specifically you're trying to do here with the classValdd variable at the end of the function block, but with function components in React, if we want to act upon a change in a state piece, we can use the built-in useEffect hook.
It should look like this -
useEffect(() => {
// Stuff we want to do whenever compClass gets updated.
}, [compClass]);
As you can see, useEffect receives 2 parameters.
The first is a callback function, the second is a dependency array.
The callback function will run whenever there is a change in the value of any of the members in that array.

Cannot update a component while rendering a different Component - ReactJS

I know lots of developers had similar kinds of issues in the past like this. I went through most of them, but couldn't crack the issue.
I am trying to update the cart Context counter value. Following is the code(store/userCartContext.js file)
import React, { createContext, useState } from "react";
const UserCartContext = createContext({
userCartCTX: [],
userCartAddCTX: () => {},
userCartLength: 0
});
export function UserCartContextProvider(props) {
const [userCartStore, setUserCartStore] = useState([]);
const addCartProduct = (value) => {
setUserCartStore((prevState) => {
return [...prevState, value];
});
};
const userCartCounterUpdate = (id, value) => {
console.log("hello dolly");
// setTimeout(() => {
setUserCartStore((prevState) => {
return prevState.map((item) => {
if (item.id === id) {
return { ...item, productCount: value };
}
return item;
});
});
// }, 50);
};
const context = {
userCartCTX: userCartStore,
userCartAddCTX: addCartProduct,
userCartLength: userCartStore.length,
userCartCounterUpdateCTX: userCartCounterUpdate
};
return (
<UserCartContext.Provider value={context}>
{props.children}
</UserCartContext.Provider>
);
}
export default UserCartContext;
Here I have commented out the setTimeout function. If I use setTimeout, it works perfectly. But I am not sure whether it's the correct way.
In cartItemEach.js file I use the following code to update the context
const counterChangeHandler = (value) => {
let counterVal = value;
userCartBlockCTX.userCartCounterUpdateCTX(props.details.id, counterVal);
};
CodeSandBox Link: https://codesandbox.io/s/react-learnable-one-1z5td
Issue happens when I update the counter inside the CART popup. If you update the counter only once, there won't be any error. But when you change the counter more than once this error pops up inside the console. Even though this error arises, it's not affecting the overall code. The updated counter value gets stored inside the state in Context.
TIL that you cannot call a setState function from within a function passed into another setState function. Within a function passed into a setState function, you should just focus on changing that state. You can use useEffect to cause that state change to trigger another state change.
Here is one way to rewrite the Counter class to avoid the warning you're getting:
const decrementHandler = () => {
setNumber((prevState) => {
if (prevState === 0) {
return 0;
}
return prevState - 1;
});
};
const incrementHandler = () => {
setNumber((prevState) => {
return prevState + 1;
});
};
useEffect(() => {
props.onCounterChange(props.currentCounterVal);
}, [props.currentCounterVal]);
// or [props.onCounterChange, props.currentCounterVal] if onCounterChange can change
It's unclear to me whether the useEffect needs to be inside the Counter class though; you could potentially move the useEffect outside to the parent, given that both the current value and callback are provided by the parent. But that's up to you and exactly what you're trying to accomplish.

setState inside for loop does not update the state: React+Typescript

I am having an array of objects which is a state object that corresponds to a user account. Just for a validation purpose if any one of the object property within array is empty, I am setting a validation property of state object to false. Since I am looping using for loop, the setState is not setting data.
this.state = {
accounts: [{firstname:"",lastname:"",age:""}],
validated: true
};
onAdd = () => {
let { accounts,validated } = this.state;
for(let i=0; i< accounts.length; i++){
if(accounts[i].firstname === '' || accounts[i].age === '')
{
this.setState({validated: false},() => {}); // value is not getting set
break;
}
}
if(validated)
{
// some other operation
this.setState({validated: true},() => {});
}
}
render(){
let { validated } = this.state;
return(
{validated ? <span>Please fill in the details</span> : null}
//based on the validated flag displaying the error message
)
}
Try to have a separate check that goes through the array in the state and depending if the data is missing or not you can have the temporary variable be either true or false. Then based on the temp variable you can set the state accordingly.
Let me know if this works:
onAdd = () => {
let { accounts,validated } = this.state;
let tempValidate = true; //assume validate is true
for(let i=0; i< accounts.length; i++){
if(accounts[i].firstname === '' || accounts[i].age === '')
{
//this loop will check for the data validation,
//if it fails then you set the tempValidate to false and move on
tempValidate = false
break;
}
}
// Set the state accordingly
if(tempValidate)
{
this.setState({validated: true},() => {});
} else {
this.setState({validated: false},() => {});
}
}
There are multiple issues in your code. First of all this.setState is asynchronous and therefore you can not say when it really is executed.
So if you call
this.setState({validated: false},() => {});
you can not rely that after that this.state.validated is false immediatly.
So after you call this.setState to make validated == false and then check:
if(this.state.validated)
{
// some other operation
this.setState({validated: true},() => {});
}
It might be either true or false.
But in your code you're extracting validated (what you're using in the if-condition) at the beginning of your method. That means validated won't change when this.state changes.
You might want to use the callback you already added (() => {}) to perform some other action after the state changed or just use a normal valiable instead of something related to the state.
Like:
tmp = true;
for-loop {
if(should not validate) {
tmp = false;
this.setState({validated: false});
break;
}
if(tmp) {
this.setState({validated: true},() => {});
}
setState is async function. So it won't work in loops.
From the docs
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
Also refer this.

What is proper way to call function from onClick so it don't trigger wrong one?

When I click on button then onClick triggers correct function, run half through and jumps to other function which is not related to it and run through half of it and jumps back to first function, runs half trough again and drops error
Uncaught TypeError: _this.state.searchValue.toLowerCase is not a function
Interesting part is that I click other button before which triggers this function with toLowerCase() and there is no errors.
I dont have any idea whats going on here but so far i was trying to remove few lines to see which line cause it because I dont think that line with toLowerCase() realy is the reason. Everything works when I remove lines where is first this.setState.
Here is my function:
( Alerts is used to track where function is at, that how i know
that it run half through only. It never reach alert("DDD").
This function is which is triggered with button onClick like it should be )
onSelect = (e) => {
const data = e.target.getAttribute('data-id');
const itemId = e.target.getAttribute('data-id');
const itemIdState = !this.state[e.target.getAttribute('data-id')];
alert("AAA")
this.setState(state => { // <--- Somehow problem comes from this setState function
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
alert("CCC")
this.setState(State => ({
[itemId]: itemIdState,
}), function() {
alert("DDD")
if(this.state[itemId] === true){
this.setState({isAnySelected: true})
}else if(this.state[itemId] === false){
this.setState({isAnySelected: false})
}
})
}
This is other function which is triggered by mistake and is not related to other. It is just returning component which is displayed and when I press on its button then i have this issue.
filterSearch = (id, title, path) => {
let name = title.toLowerCase()
let filter = this.state.searchValue.toLowerCase()
if(name.includes(filter)){
return <SearchResult key={id} data-id={id} pName={path} onClick={this.onSelect} selected={this.state[id]} />
}
}
And here is from where filterSearch is triggered. Behind this.props.searchResult is Redux.
{this.props.searchResult ? this.props.searchResult.map(category =>
this.filterSearch(category.id, category.title, category.path)
) : null
}
I think I see what the problem is: in your problematic this.setState, you cast everything in your state to a boolean:
this.setState(state => {
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
Your for() statement ends up comparing searchValue to data (some kind of ID), which I imagine more often than not will not be the case, so searchValue ends up getting set to false.
And what happens when you try to do .toLowerCase() on a Boolean?
To fix this, consider structuring your state like this:
this.state = {
searchValue: '',
ids: {},
};
Then, replace your problematic this.setState with something like this:
this.setState((state) => {
const newIDs = {
// Create a clone of your current IDs
...state.ids,
};
Object.keys(newIDs).forEach(key => {
newIDs[key] = key === data
});
alert("BBB")
return {
// searchValue will remain untouched
...state,
// Only update your IDs
ids: newIDs,
}
});
What exactly are you wanting to do here?
this.setState(state => {
const newState = {}; // You are initializing an object
for (const dataId in state) {
newState[dataId] = dataId === data // You are putting in an array every property of state that is equal to data
}
return newState
});
So irrevocably, your this.state.searchValue property will be changed to something else, which is of boolean type. So toLowerCase being a function for string.prototype, you will get an error.
You should describe what you where aiming to get here.

Resources