State value not updating in react - reactjs

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.

Related

How do you pass a React callback function to a typescript object?

I have a singleton class in Daemon.ts
export default class Daemon {
private static instance : Daemon;
callback : (tstring : string)=>void;
t : number;
constructor (callback: (tstring : string)=>void){
this.data = data
this.callback = callback;
this.t = 0;
window.setInterval(this.tick.bind(this), 1000)
}
public static getInstance(callback: (tstring : string)=>void){
if(!Daemon.instance){ Daemon.instance = new Daemon(callback);}
return Daemon.instance;
}
tick(){
this.t = this.t + 1
this.callback(this.t.toString());
}
}
Then in a separate file, Timefeedback.tsx, I have:
const TimeFeedback = () => {
const [time, updateSimTime] = React.useState<string>("starting string");
const updateTime = (tString : string) => {
updateSimTime(tString);
console.log(`update time called, val: ${tString}`)
}
const daemon = Daemon.getInstance(updateTime.bind(this));
return (
<div>
{time}
</div>
);
};
What I expect to happen:
the time state is updated on each tick() from the daemon.
What actually happens:
the callback function updateTime is successfully called, and the console log prints the correct values. But the setState function updateTime()
What happens is I get console logs from TimeFeedback.tsx with the expected value of tString printed out. But the setState function setSimTime never gets called, so the text in the div remains "starting time". Why is this?
I have investigated, when calling print events inside of Daemon I get:
function() {
[native code]
}
I tried to remove the .bind(this) inside TimeFeedback.tsx, but nothing changes except the print instead says:
tString => {
setSimTime(tString);
console.log(`update time called, val: ${tString}`);
}
I also walked through with the debugger and updateSimTime does get called but it has no effect.
Why is that updateSimTime has no effect?
No need to .bind in the component
updateTime.bind(this) doesn't make sense in a function component because a function component doesn't have a this like a class component does. Your updateTime function can be passed directly to the Daemon.getInstance function.
You also need to remove the this.data = data in the Daemon constructor because there is no data variable` (as pointed out by #Konrad in the comments).
As a best practice, I would recommend moving the code into a useEffect hook.
const TimeFeedback = () => {
const [time, updateSimTime] = useState<string>("starting string");
useEffect(() => {
const updateTime = (tString: string) => {
updateSimTime(tString);
console.log(`update time called, val: ${tString}`);
};
Daemon.getInstance(updateTime);
}, [updateSimTime]);
return <div>{time}</div>;
};
CodeSandbox Link

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 not maintaining set value after value set with Callback

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);
};

react setState not updating state in one of my functions

I'm working an a react app with a few forms and I am trying to implement an edit form for input items. The function first opens the list item in a pre-populated form.
The editItem function currently looks like this:
editItem(event) {
event.preventDefault();
const target = event.target.parentNode.parentNode;
const { key } = target.dataset;
const { className } = target;
const currState = { ...this.state[className] };
const currItem = currState.list[key];
for (let i in currItem) {
if (i !== "list" && i !== "hidden") {
currState[i] = currItem[i]
}
}
this.setState({ [className]: currState });
this.hideUnhide({target: {name: className}});
}
I have confirmed with console logs that currState is correctly set with the values that I am looking for, and that I am not having an async issue. I am using this same format to set state in other functions in my app and all of the others are working properly. If I directly mutate state in the same place, I get the behavior I'm looking for (form fields populate), but nothing happens when I use setState.
Link to my github repo: here. The function in question is in App.js.
As Brian Thompson points out in his comment, it turns out that the hideUnhide function call directly after my setState uses setState as well and writes over the first setState call with the previous state:
hideUnhide(event) {
const { name } = event.target;
const currState = { ...this.state[name] };
if (currState.hidden === true) {
currState.hidden = false;
}
this.setState({ [name]: currState });
}
The way to prevent that was to use hideUnhide as a callback to the setState in editItem:
this.setState({ [className]: currState }, () =>
this.hideUnhide({ target: { name: className } })
);
and now everything functions as intended.

React local element not updating?

I have a component which has a local variable
let endOfDocument = false;
And I have a infinite scroll function in my useEffect
useEffect(() => {
const { current } = selectScroll;
current.addEventListener('scroll', () => {
if (current.scrollTop + current.clientHeight >= current.scrollHeight) {
getMoreExercises();
}
});
return () => {
//cleanup
current.removeEventListener('scroll', () => {});
};
}, []);
In my getMoreExercises function I check if we reached the last document in firebase
function getMoreExercises() {
if (!endOfDocument) {
let ref = null;
if (selectRef.current.value !== 'All') {
ref = db
.collection('exercises')
.where('targetMuscle', '==', selectRef.current.value);
} else {
ref = db.collection('exercises');
}
ref
.orderBy('average', 'desc')
.startAfter(start)
.limit(5)
.get()
.then((snapshots) => {
start = snapshots.docs[snapshots.docs.length - 1];
if (!start) endOfDocument = true; //Here
snapshots.forEach((exercise) => {
setExerciseList((prevArray) => [...prevArray, exercise.data()]);
});
});
}
}
And when I change the options to another category I handle it with a onChange method
function handleCategory() {
endOfDocument = false;
getExercises();
}
I do this so when we change categories the list will be reset and it will no longer be the end of the document. However the endOfDocument variable does not update and getMoreExercise function will always have the endOfDocument value of true once it is set to true. I cannot change it later. Any help would be greatly appreciated.
As #DevLoverUmar mentioned, that would updated properly,
but since the endOfDocument is basically never used to "render" anything, but just a state that is used in an effect, I would put it into a useRef instead to reduce unnecessary rerenders.
Assuming you are using setExerciseList as a react useState hook variable. You should use useState for endOfDocument as well as suggested by Brian Thompson in a comment.
import React,{useState} from 'react';
const [endOfDocument,setEndOfDocument] = useState(false);
function handleCategory() {
setEndOfDocument(false);
getExercises();
}

Resources