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!
Related
I have a Task component which shows what task i need to do and when, and i want this to auto update when its time for it to change. So i was trying to do something like this
function Task(){
const [data, setData] = useState();
const [currentTask, setTask] = useState();
React.useEffect(() => {
fetch("/api")
.then((res) => res.json())
.then((data) => setData(data.tasks));
}, []);
function determineTask(){
Object.keys(data).map(function(key, index){
if(msSinceDayBegan > data[key].startMs && msSinceDayBegan < data[key].endMs){
setTask(data[key].task);
const nextIndex = Object.keys(data)[index+1];
setTimeout(determineTask, (data[nextIndex].startMs - msSinceDayBegan))
}
})
}
if(data && !initialized){
initialized = true;
determineTask();
}
console.log(currentTask);
return(
<div>
{console.log("inside return")}
<p>{currentTask? currentTask: "Loading..."}</p>
</div>
)
}
when I log currentTask I get back the result I want. However, before that, there are a few logs of undefined, but at the end, it prints out the actual value of the task and then logs "inside return". But the only thing getting rendered on my app is "loading...", why is this? currentTask has a value, am I missing something? I've tried all that I could but I couldn't understand it.
Thank you for your help!
This is happening because the function determineTask() is being called before the data is fetched. Data fetching often takes a couple seconds, and the determineTask() function is being called as soon as the component renders.
I would suggest putting the block of code:
function determineTask(){
Object.keys(data).map(function(key, index){
if(msSinceDayBegan > data[key].startMs && msSinceDayBegan < data[key].endMs){
setTask(data[key].task);
const nextIndex = Object.keys(data)[index+1];
setTimeout(determineTask, (data[nextIndex].startMs - msSinceDayBegan))
}
})
}
if(data && !initialized){
initialized = true;
determineTask();
}
console.log(currentTask);
inside a useEffect. useEffect has a second parameter of a dependency array that will trigger the useEffect to run again when one of the dependencies change.
In your first useEffect, you're changing the data state variable and then you want the currentTask state variable to change once you've retrieved the data.
So, your second useEffect will look something like:
useEffect(() => {
function determineTask(){
Object.keys(data).map(function(key, index){
if(msSinceDayBegan > data[key].startMs && msSinceDayBegan < data[key].endMs){
setTask(data[key].task);
const nextIndex = Object.keys(data)[index+1];
setTimeout(determineTask, (data[nextIndex].startMs - msSinceDayBegan))
}
})
}
if(data && !initialized){
initialized = true;
determineTask();
}
console.log(currentTask);
},[data]);
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.
I'm running into an issue where my hobbies list is not being displayed if it was previously undefined.
The goal is to cycle through a list of hobbies and display them in the UI. If one hobby is passed, only one should be displayed. If none are passed, nothing should be displayed.
Basically, setHobbies(["Cycling"]) followed by setHobbies(undefined) correctly turns off the hobbies render, but then the next setHobbies(["Reading"]) doesn't show up.
Using a debugger I've been able to verify that the relevant code in the useEffect hook in EmployeeInfoWrapper does get triggered, and hobbiesRef.current set accordingly, but it doesn't cause EmployeeInfo to rerender.
Container:
const Container = () => {
const [hobbies, setHobbies] = React.useState<string[]>();
setLabels(["Board games"]); // example of how it's set; in reality this hook is passed down and set in lower components
return (
<SpinnerWrapper hobbies=hobbies otherInfo=otherInfo />
);
};
EmployeeInfoWrapper:
export const EmployeeInfoWrapper = (props) => {
const { hobbies, otherInfo } = props;
const [indx, setIndx] = React.useState<number>(0);
// this doesn't work due to https://github.com/facebook/react/issues/14490, shouldn't matter though because it's handled in the useEffect
const hobbyRef = React.useRef(hobbies?.length ? hobbies[indx] : "");
useEffect(() => {
if (hobbies?.length > 1) { // doubt this is relevant as my bug is with 0 or 1-length hobbies prop
hobbyRef.current = hobbies[indx];
setTimeout(() => setIndx((indx + 1) % hobbies.length), 2000);
}
if (hobbies?.length == 1) {
hobbyRef.current = hobbies[indx]; // can see with debugger this line is hit
}
if (!hobbies?.length) { // covers undefined or empty cases
hobbyRef.current = "";
}
}, [hobbies]);
return (
<EmployeeInfo hobby={hobbyRef.current} otherInfo={otherInfo} />
);
};
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.
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);
};