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]);
Related
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!
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 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 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();
}
I am rendering photos from unsplash api. And I am keeping the index of the photos to be used in the lightbox, after the initial render state of imageindex goes back to 0, how can I retain its value?
I will show some code
const ImageList = ({ image, isLoaded }) => {
const [imageIndex, setImageIndex] = useState(0);
const [isOpen, setIsOpen] = useState('false');
const onClickHandler = (e) => {
setIsOpen(true);
setImageIndex(e.target.id);
};
const imgs = image.map((img, index) => (
<img
id={index}
key={img.id}
src={img.urls.small}
onClick={onClickHandler}
if (isOpen === true) {
return (
<Lightbox
onCloseRequest={() => setIsOpen(false)}
mainSrc={image[imageIndex].urls.regular}
onMoveNextRequest={() => setImageIndex((imageIndex + 1) % image.length)}
onMovePrevRequest={() => setImageIndex((imageIndex + image.length - 1) % image.length)}
nextSrc={image[(imageIndex + 1) % image.length].urls.regular}
prevSrc={image[(imageIndex + image.length - 1) % image.length].urls.regular}
/>
after the initial render state, imageIndex goes back to 0.
That makes sense, the initial render would use whatever you set as the default value. You can use something like local storage to help you keep track of the index of the last used item. It's a bit primitive, but until you integrate something like Node/MongoDB for database collections, this will be perfect.
In your component, import useEffect() from React. This hook lets us execute some logic any time the state-index value changes, or anything else you might have in mind.
import React, { useEffect } from "react"
Then inside your component, define two useEffect() blocks.
Getting last used index from localStorage on intitial load:
useEffect(() => {
const lastIndex = localStorage.getItem("index", imageIndex)
setImageIndex(imageIndex)
}, []) //set as an empty array so it will only execute once.
Saving index to localStorage on change:
useEffect(() => {
localStorage.setItem("index", imageIndex)
}, [imageIndex]) //define values to subscribe to here. Will execute anytime value changes.