set time dynamic time for set interval in react - javascript

Hello i am struggling to set dynamic time for settimeout function in react js.
i have long string of key value pair of time and message. i wants to display each message for specific time and loop through whole list.
here is what i am trying, but not working.
const [timer, setTimer] = useState(0)
const [time, setTime] = useState(5000)// this is default value to start which need to update with str time value
const str=[{name:"rammy", time:1000},
{name:"james", time:4000},
{name:"crown", time:2000}]
useEffect(()=>{
const getTime= str[timer].time
setTime(getTime)
},[timer])
//when timer change it should update update time state which will be used to update time for time settime out
function increment() {
useEffect(()=>{
setTimeout(() => {
setTimer((ele)=>ele+1)
}, time);
},[timer])
} // above code is for increment time state on each iteration
function ButtonHandle(){
//setRealString(itr)
increment()
} //button handler for start timer

First of all, you can't put hooks inside functions (other than your component functions). https://reactjs.org/docs/hooks-rules.html
So take the useEffect out of increment()
useEffect(()=>{
increment()
},[timer])
function increment() {
setTimeout(() => {
setTimer((ele)=>ele+1)
}, time);
}
But you also need to clear the timeout. We can return the timeout function to reference it, and clear the time out with a return inside useEffect. Clearing timeouts and intervals in react
useEffect(()=>{
const myTimeout = increment()
return () => {
clearTimeout(myTimeout)
}
},[timer])
function increment() {
return setTimeout(() => {
setTimer((ele) => ele + 1);
}, time);
}
Then we can combine the useEffects which both have a dependancy array of [timer].
useEffect(() => {
const getTime = str[timer].time;
setTime(getTime);
const myTimeout = increment();
return () => {
clearTimeout(myTimeout);
};
}, [timer]);

You don't need to use useEffect to do it. You misunderstood the useEffect usage, it's a react hook to you implement side-effects and you can't use react-hooks inside a function, it should be in the component scope.
I can increment directly from the ButtonHandle function.
// On the index state is necessary in this implementation
const [index, setIndex] = useState(-1)
const guys=[
{name: "rammy", time:1000},
{name: "james", time:4000},
{name: "crown", time:2000}
]
// useCallback memoize the increment function so that it won't
// change the instance and you can use it in the useEffect
// dependency array
const increment = useCallback(() => {
setIndex((i) => i+1)
}, [])
useEffect(() => {
// change the state if the timer is greater than -1
if (index !== -1) {
if (index >= guys.length) {
setIndex(-1);
} else {
setTimeout(() => {
increment();
}, guys[index].time); // <-- you get the time from your array
}
}
}, [index, increment]);
function handleClick(){
//setRealString(itr)
increment()
}
Even though I helped you, I don't know what you're trying to do. This implementation sounds like a code smell. We can help you better if you explain the solution you're trying to do instead of just the peace of code.
You don't need to set the time state, as you already have the time in the array; avoiding unnecessary state changes is good.

Related

ReactJS counter or timer with Start or Stop Button

Need help to understand in useEffect, if I don't put counter and timerCheck in useEffect dependency then what it will effect here.
And if I put timerCheck dependency in useEffect then counter increasing 100 time faster
Also how can i run this code without any error or warning
code in codesandbox
const [counter, setCounter] = useState(0);
const [startcounter, setStartcounter] = useState(false);
const [timerCheck, setTimerCheck] = useState(0);
useEffect(() => {
let intervalStart = null;
if (startcounter) {
intervalStart = setInterval(() => {
setCounter((prevState) => (prevState += 1));
}, 1000);
setTimerCheck(intervalStart);
} else {
clearInterval(timerCheck);
}
return () => {
if (counter !== 0) clearInterval(timerCheck);
};
}, [startcounter]);
const handleStartButton = () => {
setStartcounter(true);
};
const handleStopButton = () => {
setStartcounter(false);
};
Try not to declare unnecessary states. timerCheck state is redundant.
You want to start an interval that increases counter by one every second.
When user stops the stopwatch , you want the interval to be cleared, so it would stop increasing the timer.
So your only dependancy in useEffect would be whether your stopwatch is running or not. And the only thing that matters to you, is when startCounter is ture. You start an interval when it is true and clear the interval in its cleanup.
useEffect(() => {
let intervalStart = null;
if (!startcounter) {
return () => {};
}
intervalStart = setInterval(() => {
setCounter((prevState) => (prevState += 1));
}, 1000);
return () => {
clearInterval(intervalStart);
};
}, [startcounter]);
Here is the codesandbox
But you do not really need useEffect for this. IMO it would be a much better code without using useEffect.
So useEffect with no dependency will run on every render.
If you include a dependency, it will re-render and run whenever that dependency changes. So by including timerCheck in it, you're telling the page to re-render whenever that changes, which ends up changing it, causing it to re-render again, causing it to change again, etc., etc.
More info: https://www.w3schools.com/react/react_useeffect.asp

SetInterval causes too many re-renders React

Hello I would like to put setInterval in my React project to add 1 for each second but I got an error like in title of this post.
js:
const [activeTab, setActiveTab] = useState(0)
useEffect(() => {
setInterval(setActiveTab(prevTab => {
if (prevTab === 3) return 0
console.log('hi')
return prevTab += 1
}), 1000)
})
There are a few issues:
You're not passing a function to setInterval, you're calling setActiveTab and passing its return value into setInterval.
If you were passing in a function, you'd be adding a new repeated timer every time your componennt ran. See the documentation — useEffect:
By default, effects run after every completed render...
And setInterval:
The setInterval() method... repeatedly calls a function or executes a code snippet, with a fixed time delay between each call.
(my emphasis)
Starting a new repeating timer every time your component re-renders creates a lot of repeating timers.
Your component will leave the interval timer running if the component is unmounted. It should stop the interval timer.
To fix it, pass in a function, add a dependency array, and add a cleanup callback to stop the interval:
const [activeTab, setActiveTab] = useState(0);
useEffect(() => {
const handle = setInterval(() => { // *** A function
setActiveTab(prevTab => {
if (prevTab === 3) return 0;
console.log("?ghi");
return prevTab += 1;
});
}, 1000);
return () => { // *** Clear the interval on unmount
clearInterval(handle); // ***
}; // ***
}, []); // *** Empty dependency array = only run on mount
Side note: Assuming you don't need the console.log, that state setter callback function can be simpler by using the remainder operator:
setActiveTab(prevTab => (prevTab + 1) % 3);
useEffect(() => {
setInterval(() => {
setActiveTab((prevTab) => {
if (prevTab !== 3) {
return (prevTab += 1);
} else {
return 0;
} // console.log("hi");
});
}, 1000);
}, []);

Same logic but different behaviour in 'class' and in 'functional component'

Attempted to translate an example code from class to functional component and faced the problem.
the target file is in components/Wheel/index.js
Key function that causes problem
const selectItem = () => {
if (selectedItem === null) {
const selectedItem = Math.floor(Math.random() * items.length);
console.log(selectedItem);
setSelectedItem(selectedItem);
} else {
setSelectedItem(null);
let t= setTimeout(() => {
selectItem()
}, 500);
clearTimeout(t);
}
};
First time is normal,
from second time onward,
2 clicks are needed for the wheel to spin.
I had to add clearTimeout() or infinite loop is resulted, but the same does not happen in the original.
Original working example in class
My version in functional component.
MyVersion
Thank you.
What an excellent nuance of hooks you've discovered. When you call selectItem in the timeout, the value of selectedItem that is captured in lexical scope is the last value (not null).
There's two answers, a simple answer and a better working answer.
The simple answer is you can accomplish it be simply separating the functions: https://codesandbox.io/s/spinning-wheel-game-forked-cecpi
It looks like this:
const doSelect = () => {
setSelectedItem(Math.floor(Math.random() * items.length));
};
const selectItem = () => {
if (selectedItem === null) {
doSelect();
} else {
setSelectedItem(null);
setTimeout(doSelect, 500);
}
};
Now, read on if you dare.
The complicated answer fixes the solution for the problem if items.length may change in between the time a timer is set up and it is fired:
https://codesandbox.io/s/spinning-wheel-game-forked-wmeku
Rerendering (i.e. setting state) in a timeout causes complexity - if the component re-rendered in between the timeout, then your callback could've captured "stale" props/state. So there's a lot going on here. I'll try and describe it as best I can:
const [selectedItem, setSelectedItem] = useState(null);
// we're going to use a ref to store our timer
const timer = useRef();
const { items } = props;
// this is just the callback that performs a random select
// you can see it is dependent on items.length from props
const doSelect = useCallback(() => {
setSelectedItem(Math.floor(Math.random() * items.length));
}, [items.length]);
// this is the callback to setup a timeout that we do
// after the user has clicked a "second" time.
// it is dependent on doSelect
const doTimeout = useCallback(() => {
timer.current = setTimeout(() => {
doSelect();
timer.current = null;
}, 500);
}, [doSelect]);
// Here's the tricky thing: if items.length changes in between
// the time we rerender and our timer fires, then the timer callback will have
// captured a stale value for items.length.
// The way we fix this is by using this effect.
// If items.length changes and there is a timer in progress we need to:
// 1. clear it
// 2. run it again
//
// In a perfect world we'd be capturing the amount of time remaining in the
// timer and fire it exactly (which requires another ref)
// feel free to try and implement that!
useEffect(() => {
if (!timer.current) return;
clearTimeout(timer.current);
doTimeout();
// it's safe to ignore this warning because
// we know exactly what the dependencies are here
}, [items.length, doTimeout]);
const selectItem = () => {
if (selectedItem === null) {
doSelect();
} else {
setSelectedItem(null);
doTimeout();
}
};

React native, how to stop infinitely running setInterval when a particular state variable value changes

In my react native project I need to create a blinking (like warning) text. For this I use setInterval inside useffect() where I switch the text visibility by using setShowText((showText) => !showText). Now I need to to stop the setInterval when another state variable stopTriger changes to true. (I have a sperate method to set setStopTriger). Now the situation is like console.log('timer is running infinitely'). How can I make it to work? I use the following code, but not sure where to keep stopTriger condition:
const [stopTriger, setStopTriger] = useState(false)
const [showText, setShowText] = useState(true);
useEffect(() => {
// Change the state every second
const interval = setInterval(() => {
setShowText((showText) => !showText);
console.log('timer is running infinitely ')
// need to stop timer when stopTriger == true , how do I do it ?
}, 1000);
return () => clearInterval(interval);
}, []);
You can put intervalID in a state, and use another useEffect with stoTrigger as a dependency, when that variable is true, you can clear the interval:
Update
const [interval, setStateInterval] = useState(0);
useEffect(() => {
// Change the state every second
const interval_ = setInterval(() => {
/* set value of interval state */
setStateInterval(interval_);
setShowText(showText => !showText);
console.log('timer is running infinitely ');
// need to stop timer when stopTriger == true , how do I do it ?
}, 1000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (stopTrigger) clearInterval(interval);
}, [stopTriger]);
Also, clearInterval() will automatically take care if the interval is currently running before clearing it, so you don't need to worry
stopTriger is initially false, and you aren't modifying it.

React state repeatedly reverts back to old value

There is a component "DateForm" that changes the global state of "counterInfo" when the form is submitted.
//DateForm component submittal function.
const submitDate = () =>{
props.setCounterInfo(dateInfo); //passes date info to be counterInfo state in App.js
props.setShowInputForm(false); //DateInfo component is no longer rendered
}
then, in app.js the counterInfo state is passed to the Timer component
const App = () => {
const [showInputForm, setShowInputForm] = useState(false);
const [counterInfo, setCounterInfo] = useState(undefined);
return (
<>
<Timer
counterInfo = {counterInfo}
></Timer>
{showInputForm &&
<DateForm
setShowInputForm = {setShowInputForm}
setCounterInfo = {setCounterInfo}
></DateForm>}
</>
);
}
There is a useEffect hook inside of the Timer function that, on a one second interval, used the value of counterInfo.
//Inside the Timer Component
const [currTime, setCurrTime] = useState(null);
useEffect (() => {
setInterval(() => {
let timeLeft = (new Date(`${Months(props.counterInfo.year)[props.counterInfo.month-1].name} ${props.counterInfo.day} ${props.counterInfo.year} ${props.counterInfo.hour}:${props.counterInfo.minute}:${props.counterInfo.second}`).getTime()) - new Date().getTime();
setCurrTime(timeLeft);
},1000);
return(clearInterval());
}, [props, setCurrTime]);
What I intended to happen is for the value of timeLeft in Timer.js to update when the value of counterInfo is updated in DateForm, however, when the value is changed in DateForm, the result of both the new value of counterInfo and the old one both flash when the value of timeLeft is used in Timer.js. This issue isn't caused by any code in Timer.js becuase I tried moving the useEffect hook to app.js and passing the value down to Timer but the problem persisted. The only place that the setCounterInfo state is changed is in the DateForm component.
Does anyone have any idea how to fix this?
First, you have bit mis-syntax at interval decleration
useEffect (() => {
let interval = setInterval(() => {...},1000);
return () => clearInterval(interval);
}, [props, setCurrTime]);
But unrelated, React by default re-applies effects after every render. This is intentional and helps avoid a whole class of bugs that are present in React components.
When it comes to intervals, its specifical matters cause if a render was applied every time setInterval is called, it never will get a chance to actually run
In other words, this code might have some side effects as useEffect in each run cares only of the existing values in that time and forget everything else, and interval isn't like so.
For that from my point of view the best practice is to create useInterval custom hook, that inside will store the callback for the meanwhile
function useInterval(callback) {
const savedCallback = React.useRef();
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
function run() {
savedCallback.current();
}
let interval = setInterval(run ,1000);
return () => clearInterval(interval);
}, [])
}
//Inside the Timer Component
const [currTime, setCurrTime] = useState(null);
useInterval(()=>
setCurrTime((new Date(`${Months(props.counterInfo.year)[props.counterInfo.month-1].name} ${props.counterInfo.day} ${props.counterInfo.year} ${props.counterInfo.hour}:${props.counterInfo.minute}:${props.counterInfo.second}`).getTime()) - new Date().getTime()))

Categories

Resources