this is the fetchData function and it works the first time, the table updated 10 rows, but when you reach the end again nothing happens . what is the reason for this to work the first time only and not updating after ?
const [items , setItems] = useState(data);
const [hasMore , setHasMore] = useState(true)
const [offset, setOffset] = useState(10);
const fetchMoreData = () => {
if (items.length >= items.length +1) {
setHasMore( false );
return;
}
setTimeout(() => {
setOffset( offset + 10);
}, 100);
};
You would need to provide a bit more code than this, but I suppose what you'll want to do is listen for changes in useEffect, and based on a condition, call your fetch function
Related
I'm working on a Pomodoro clock. To build the countdowns I'm using useEffect and setTimeout. Everything seemed to be fine until I realized there's a 30ms to 50ms delay between every second of the clock. How would I set it to pricesily update the clock at every 1000ms?
I'm using useState to handle the clock's time and controls to stop, pause and reset. They're all working properly. It's just the second's timing that is delaying more that it should.
function App() {
const [workTarget, setWorkTarget] = useState(25 * 60);
const [breakTarget, setBreakTarget] = useState(5 * 60);
const [time, setTime] = useState(workTarget); //time in seconds
const [counting, setCounting] = useState(false);
const [working, setWorking] = useState(true);
const [clockTarget, setClockTarget] = useState(workTarget);
const [combo, setCombo] = useState(0);
const [config, setConfig] = useState(false);
const [playWork] = useSound(workSfx);
const [playBreak] = useSound(breakSfx);
const [playPause] = useSound(pauseSfx);
let tick = 1000;
let timeout;
let timenow = Date.now();
// Handle pause and stop of countdown
useEffect(() => {
if (time > 0 && counting === true) {
timeout = setTimeout(() => {
setTime(time - 1);
console.log(timenow);
}, tick);
} else if (time === 0 && counting === true) {
setWorking(!working);
if (working === true) {
playBreak();
setTime(breakTarget);
setClockTarget(breakTarget);
} else {
playWork();
setCombo(combo + 1);
setTime(workTarget);
setClockTarget(workTarget);
}
}
if (!counting || config) {
clearTimeout(timeout);
}
});
}
export default App;
This is not the complete code. I cut off other components for buttons and stuff that don't relate to this.
We can't ensure its EXACTLY 1000 ms. Behind the scenes, react state updates use setTimeout, and to change the timer, you need to use either setTimeout or setInterval. setTimeout and setInterval only ensure that the code inside will not run for a delay period, and then executes when the main thread is not busy.
Therefore, it's impossible to ensure that every update is EXACTLY 1000ms. There will usually be 30-50ms delay.
However, that doesn't mean your timer will be inaccurate or unreliable. It just depends how you initialize it.
Below is how I would improve the code you provided, as right now clearing the timeout adds some extra overhead, and batching the state updates would lead to improved performance in this case.
let tick = 1000;
//countdown modifying behavior every 1000ms. Active clock
useEffect(() => {
if (time > 0 && counting === true) {
setTimeout(() => {
setTime((state) => state - 1);
console.log(timenow);
}, tick);
}
}, [time, counting]);
//pausing, stopping and resuming
useEffect(() => {
if (time === 0 && counting === true) {
setWorking(!working);
if (working === true) {
playBreak();
unstable_batchedUpdates(() =>{
setTime(breakTarget);
setClockTarget(breakTarget);
}
} else {
playWork();
//update all at once
unstable_batchedUpdates(() =>{
setCombo(combo + 1);
setTime(workTarget);
setClockTarget(workTarget);
}
}
}
}, [time, counting]);
Further improvements to keep the clock in sync
On component mount, grab the current time for start time, and calculate the end time and store both
At every setTimeout in your useEffect, using your stored start time and current time, calculate how much time there is left and set state to that.
//countdown time modifying behavior every 1000ms
const current = new Date()
const [start, setStart] = useState(current)
//this example uses a 25 minute timer as an example
const [end, setEnd] = useState(new Date(start.getTime() + 25 * 60000))
const tick = 1000
useEffect(() => {
if (time > 0 && counting === true) {
setTimeout(() => {
const current = new Date()
const diffTime = current.getDate() - end.getDate()
const timeRemainingInMins = Math.ceil(diffTime / (1000*60));
setTime(timeRemainingInMins);
}, tick);
}
}, [time, counting]);
I may have an idea.
Currently your useEffect hook is set up to run on every render which means every time you change the Time you render the page and rerun the useEffect hook, however you seem to have quite a bit of logic inside the hook, plus react may not render consistently which means that there may be some inconsistencies with your timing.
I suggest using setInterval instead and running the useEffect hook only once at the start to set up the interval using useEffect(() => {...}, []) (notice [] as the second argument)
For the rest of your logic you can always create another useEffect hook that updates when the components change.
With all that in mind your timing logic should look something like this:
useEffect(() => {
interval = setInterval(() => {
setTime(time-1);
console.log(timenow);
}, tick); //runs once every tick
return(() => clearInterval(interval)); //Once the component unmounts run some code to clear the interval
}, [])
Now in another useEffect hook you can watch for changes to time and update the rest of your app accordingly.
This approach should be much more reliable because it doesn't depend on react to update the time.
#The_solution
const component =() => {
const [val, setval]= useState(globalState.get());
useEffect(() => {
setVal(globalState.get());
globalState.subscribe(newVal=> setVal(newVal));
});
return <span>{val}</span>
}
I am using useEffect to get the total jigsaw pieces from my async storage to be displayed on the home page. I use async storage to get the number of jigsaws stored in the storage then for each jigsaw, i add the total amount of jigsaw pieces. I do this within useEffect as the total amount of jigsaw pieces may change depending on if the user adds more jigsaw to their collection. However, when i use useEffect, my total amount of jigsaw pieces constantly updates and never stops.
Code:
let [totalPieces, setTotalPieces] = useState(0);
const [numJigsaw, setNumJigsaw] = useState([]);
const getNumOfJigsaw = async () => {
try {
setNumJigsaw(await AsyncStorage.getAllKeys());
} catch (e) {}
return numJigsaw;
};
const getAllJigsaw = async () => {
let jigsaws = await getNumOfJigsaw();
for (let z = 0; z < jigsaws.length; z++) {
let currentJigsaw = JSON.parse(await AsyncStorage.getItem(jigsaws[z]));
setTotalPieces(totalPieces+ parseFloat(currentJigsaw.pieces));
}
};
useEffect(() => {
getAllJigsaw();
}, [totalPieces]);
I believe the issue is because totalPieces is dependant? Im not entirely sure though.
Yes - you've set the effect to run when totalPieces changes (via the dependency).
The effect (when the async stuff resolves) sets totalPieces, so the effect is run again due to that dependency, etc, etc.
It sounds like you're looking for something like
const [totalPieces, setTotalPieces] = useState(0);
const [jigsawKeys, setJigsawKeys] = useState([]);
useEffect(() => {
(async () => {
const jigsaws = await AsyncStorage.getAllKeys();
let total = 0;
for (let z = 0; z < jigsaws.length; z++) {
let currentJigsaw = JSON.parse(await AsyncStorage.getItem(jigsaws[z]));
total += parseFloat(currentJigsaw.pieces);
}
setJigsawKeys(jigsaws);
setTotalPieces(total);
})();
}, [totalPieces]);
all in all (I renamed numJigsaw to jigsawKeys so it makes more sense).
I am using React-id-swiper to load images for products. Let's say I have 5 products.
I am trying to use a function to detect when every image is successfully loaded, then when they are, set the useState 'loading' to false, which will then display the images.
I am using a variable 'counter' to count how many images there are to load. Once 'counter' is more than or equal to the amount in the list (5), set 'loading' to FALSE and load the images.
However, I am noticing that when I console log 'counter' when running it from useEffect, it ends at '0' and renders 5 times. 'Loading' also remains true.
However, if I trigger a re-render, I see 1,2,3,4,5 in the console. 'Loading' then turns to false.
I suspect this is to do with useEffect being too fast, but I can't seem to figure it out. Can somebody help?
Code:
const { products } = useContext(WPContext);
const [productsList, setProductsList] = useState(products);
const [filteredList, setFilteredList] = useState(products);
const [loading, setLoading] = useState(false);
useEffect(() => {
loadImages(products);
}, []);
const loadImages = (allProducts) => {
let counter = 0;
setLoading(true);
const newProducts = allProducts.map((product) => {
const newImg = new Image();
newImg.src = "https://via.placeholder.com/450x630";
newImg.onload = function () {
counter += 1;
};
// At this point, if I console log 'counter', it returns as 0. If I trigger a re-render, it returns as 1,2,3,4,5
return {
...product,
acf: {
...product.acf,
product_image: {
...product.acf.product_image,
url: newImg.src,
},
},
};
});
handleLoading(newProducts, counter);
};
const handleLoading = (newProducts, counter) => {
if (counter >= filteredList.length) {
setLoading(false);
counter = 0;
setFilteredList(newProducts);
}
};
First weird thing is that you are calling the handleLoading() function before its even defined. This is probably not a problem but a weird practice.
You are also creating a new variable counter when you pass it in as an argument. You aren't actually reseting the counter that you want.
Also using onload is unnecessary and can cause weird behavior here since its not a synchronous operation but event based. As long as you set the img.src it should force it to load.
Try this:
const loadImages = (allProducts) => {
let counter = 0;
setLoading(true);
const newProducts = allProducts.map((product) => {
const newImg = new Image();
newImg.src = "https://via.placeholder.com/450x630";
counter += 1;
return { ...product, acf: { ...product.acf, product_image: { ...product.acf.product_image, url: newImg.src }}};
});
const handleLoading = (newProducts) => {
if (counter >= filteredList.length) {
setLoading(false);
counter = 0;
setFilteredList(newProducts);
}
};
handleLoading(newProducts);
};
useEffect(() => {
loadImages(products);
}, []);
I am building an animation where the letters of two words appear one by one, similar to a slide-in effect. I have the code made with jQuery, but I need to implement it in my React app (built with hooks). The code that I have takes the text, splits it creating individual letters, and adds spans between those letters. This is the following code that I need to convert to React:
const logoText = document.querySelector('.logo');
const stringText = logoText.textContent;
const splitText = stringText.split("");
for (let i=0; i < splitText.length; i++) {
text.innerHTML += "<span>" + splitText + "</span>"
}
let char = 0;
let timer = setInterval(onTick, 50)
I was wondering if you guys could help me figure it out. Thanks a lot!
You need to iterate over the text and create a timeout function for every letter with a different time of execution, that way will be visible the slide effect you are expecting:
Custom hook
const useSlideInText = text => {
const [slide, setSlide] = useState([]);
useEffect(() => {
Array.from(text).forEach((char, index) => {
const timeout = setTimeout(
() =>
setSlide(prev => (
<>
{prev}
<span>{char}</span>
</>
)),
index * 100
);
});
}, []);
return slide;
};
Usage
function App() {
const slide = useSlideInText("hello");
return (
<div>
{slide}
</div>
);
}
Working example
I am assuming the React components that you want to run this hook in possess the text you want to split. I am also assuming that on the interval, you want to reveal more of the text. In that case my example solution would look like this:
Hook
import {useState, useEffect} from "react";
const useSlideInText = (text) => {
const [revealed, setRevealed] = useState(0);
useEffect(() => {
if (revealed < text.length) {
setTimeout(() => setRevealed(revealed + 1), 50);
}
});
return text.split('').slice(0, revealed).map((char) => (<span>{char}</span>));
}
Example usage
const MyComponent = (props) => {
const displayText = useSlideInText(props.text);
return <div>{displayText}</div>;
};
going off of the other answer:
const generateDisplayTest = (text, numChars) => text.split('').slice(0, numChars).map((char) => (<span>{char}</span>));
const MyComponent = (props) => {
const [revealed, setRevealed] = useState(0);
useEffect(() => {
if (revealed < props.text.length) {
setTimeout(() => setRevealed(revealed + 1), 50);
}
}, [revealed]);
const displayText = generateDisplayTest(props.text, revealed);
return <div>{displayText}</div>;
};
including [revealed] in the useEffect means that useEffect will run every time that revealed changes. Also I always feel that useState/useEffect should live on the component, it has been that way in the place I worked but I'm not sure if that is industry standard.
I am pulling documents from Firebase, running calculations on them and separating the results into an array. I have an event listener in place to update the array with new data as it is populated.
I am using setTimeout to loop through an array which works perfectly with the initial data load, but occasionally, when the array is updated with new information, the setTimeout glitches and either begins looping through from the beginning rather than continuing the loop, or creates a visual issue where the loop doubles.
Everything lives inside of a useEffect to ensure that data changes are only mapped when the listener finds new data. I am wondering if I need to find a way to get the setTimeout outside of this effect? Is there something I'm missing to avoid this issue?
const TeamDetails = (props) => {
const [teamState, setTeamState] = useState(props.pushData)
const [slide, setSlide] = useState(0)
useEffect(() => {
setTeamState(props.pushData)
}, [props.pushData])
useEffect(()=> {
const teams = teamState.filter(x => x.regData.onTeam !== "null" && x.regData.onTeam !== undefined)
const listTeams = [...new Set(teams.map((x) => x.regData.onTeam).sort())];
const allTeamData = () => {
let array = []
listTeams.forEach((doc) => {
//ALL CALCULATIONS HAPPEN HERE
}
array.push(state)
})
return array
}
function SetData() {
var data = allTeamData()[slide];
//THIS FUNCTION BREAKS DOWN THE ARRAY INTO INDIVIDUAL HTML ELEMENTS
}
SetData()
setTimeout(() => {
if (slide === (allTeamData().length - 1)) {
setSlide(0);
}
if (slide !== (allTeamData().length - 1)) {
setSlide(slide + 1);
}
SetData();
console.log(slide)
}, 8000)
}, [teamState, slide]);