const handleClick = () => {
setState({ ...state, on: !state.on });
let tog = state.on;
console.log("first" + tog);
const interval = setInterval(() => {
if (tog) {
console.log(tog);
} else clearInterval(interval);
}, 1000);
};
enter image description here
this one will not be able to stop even the tog is false;
however if I don't use state, change to a variable it will not happen,
it is so weird for me, I need some help;
let flag = true;
const handleClick = () => {
flag = !flag;
console.log("first" + flag);
const interval = setInterval(()=>{
if(flag){
console.log(flag);
}else(clearInterval(interval))
},1000)
};
React will create new handleClick on every re-renders, and there will be different setIntervals,
const intvl = useRef(null);
const handleClick = () => {
intvl.current = setInterval(() => { //now interval is not changing on every fn recreation
.....
clearInterval(intvl.current);
}
check this one
You should use an Effect to handle intervals, something like this:
useEffect(() => {
let intervalId
if (state.on) {
intervalId = setInterval(() => {
console.log(state.on)
}, 1000)
return () => clearInterval(intervalId)
}
}, [state.on])
const handleClick = () => {
setState({ ...state, on: !state.on });
};
working code
Related
Is there any way to know whether the strict mode is enabled in React?
useEffect(() => {
if(strictModeEnabled) {}
})
I didn't find any information in the docs.
You can use this
export const useEffectOnce = ( effect )=> {
const destroyFunc = useRef();
const effectCalled = useRef(false);
const renderAfterCalled = useRef(false);
const [val, setVal] = useState(0);
if (effectCalled.current) {
renderAfterCalled.current = true;
}
useEffect( ()=> {
// only execute the effect first time around
if (!effectCalled.current) {
destroyFunc.current = effect();
effectCalled.current = true;
}
// this forces one render after the effect is run
setVal(val => val + 1);
return ()=> {
// if the comp didn't render since the useEffect was called,
// we know it's the dummy React cycle
if (!renderAfterCalled.current) { return; }
if (destroyFunc.current) { destroyFunc.current(); }
};
}, []);
};
call it by
useEffectOnce( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
});
Read more here
That's the warning in the console,
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Here is my code
const [index, setIndex] = useState(0);
const [refreshing, setRefreshing] = useState(false);
const refContainer: any = useRef();
const [selectedIndex, setSelectedIndex] = useState(0);
const navigation = useNavigation();
useEffect(() => {
refContainer.current.scrollToIndex({animated: true, index});
}, [index]);
const theNext = (index: number) => {
if (index < departments.length - 1) {
setIndex(index + 1);
setSelectedIndex(index + 1);
}
};
setTimeout(() => {
theNext(index);
if (index === departments.length - 1) {
setIndex(0);
setSelectedIndex(0);
}
}, 4000);
const onRefresh = () => {
if (refreshing === false) {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 2000);
}
};
What should I do to make clean up?
I tried to do many things but the warning doesn't disappear
setTimeout need to use in useEffect instead. And add clear timeout in return
useEffect(() => {
const timeOut = setTimeout(() => {
theNext(index);
if (index === departments.length - 1) {
setIndex(0);
setSelectedIndex(0);
}
}, 4000);
return () => {
if (timeOut) {
clearTimeout(timeOut);
}
};
}, []);
Here is a simple solution. first of all, you have to remove all the timers like this.
useEffect(() => {
return () => remover timers here ;
},[])
and put this
import React, { useEffect,useRef, useState } from 'react'
const Example = () => {
const isScreenMounted = useRef(true)
useEffect(() => {
isScreenMounted.current = true
return () => isScreenMounted.current = false
},[])
const somefunction = () => {
// put this statement before every state update and you will never get that earrning
if(!isScreenMounted.current) return;
/// put here state update function
}
return null
}
export default Example;
I have hook useInterval which download data every 10 seconds automaticaly, however I have also button which can manually download data in every moment. I'm struggling to restart interval timer when I click button. So basically if interval counts to 5, but I click button meantime, interval should restart and starts counting to 10 again before downloading data
const useInterval = (callback, delay) => {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => {
savedCallback.current();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
export default useInterval;
APP PART:
useInterval(() => {
getMessage();
}, 10000)
const getMessage = async () => {
setProcessing(true)
try {
const res = await fetch('url')
const response = await res.json();
setRecievedData(response)
}
catch (e) {
console.log(e)
}
finally {
setProcessing(false)
}
}
const getMessageManually = () => {
getMessage()
RESTART INTERVAL
}
You can add a reset function in the hook and return that function. The reset function should clear the existing interval and start a new one.
Here is the code for the hook which can be reset and stopped.
const useInterval = (callback, delay) => {
const savedCallback = useRef(callback);
const intervalRef = useRef(null);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay !== null) {
const id = setInterval(savedCallback.current, delay);
intervalRef.current = id;
return () => clearInterval(id);
}
}, [delay]);
useEffect(()=>{
// clear interval on when component gets removed to avoid memory leaks
return () => clearInterval(intervalRef.current);
},[])
const reset = useCallback(() => {
if(intervalRef.current!==null){
clearInterval(intervalRef.current);
intervalRef.current = setInterval(savedCallback.current,delay)
}
});
const stop = useCallback(() => {
if(intervalRef.current!==null){
clearInterval(intervalRef.current);
}
})
return {
reset,
stop
};
};
// usage
const {reset,stop} = useInterval(()=>{},10000);
reset();
stop();
You should add a reset function as returning a value from the hook.
I also fixed few issues and added an unmount handler:
// Usage
const resetInterval = useInterval(() => ..., DELAY);
resetInterval();
// Implementation
const useInterval = (callback, delay) => {
const savedCallbackRef = useRef(callback);
const intervalIdRef = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// handle tick
useEffect(() => {
const tick = () => {
savedCallback.current();
};
if (delay !== null) {
intervalIdRef.current = setInterval(tick, delay);
}
const id = intervalIdRef.current;
return () => {
clearInterval(id);
};
}, [delay]);
// handle unmount
useEffect(() => {
const id = intervalIdRef.current;
return () => {
clearInterval(id);
};
}, []);
const resetInterval = useCallback(() => {
clearInterval(intervalIdRef.current);
intervalIdRef.current = setInterval(savedCallback.current, delay)
}, [delay]);
return resetInterval;
};
Another solution is to remove the ref on the callback making the hook restart the count on every change to the callback
so updating the above solution
// Implementation
const useInterval = (callback, delay) => {
const intervalIdRef = useRef();
// handle tick
useEffect(() => {
const tick = () => {
callback();
};
if (delay !== null) {
intervalIdRef.current = setInterval(tick, delay);
}
const id = intervalIdRef.current;
return () => {
clearInterval(id);
};
}, [delay]);
// handle unmount
useEffect(() => {
const id = intervalIdRef.current;
return () => {
clearInterval(id);
};
}, []);
};
And then you can use it like this
const [counter, setCounter] = useState[0]
const onTimerFinish = useCallback(() => {
setCounter(counter + 1)
// setCounter will reset the interval
}, [counter])
useResetInterval(() => {
onTimerFinish()
}, 5000)
Is it safe to do something like the following?
const [foo, setFoo] = useState(undefined)
useEffect(() => {
dispatch(someFunc()).then(response => {
let { someFoo } = response
setFoo(someFoo)
})
}, []) // or }, [bar])
useEffect(() => {
dispatch(anotherFunc()).then(response => {
let { anotherFoo } = response
setFoo(anotherFoo)
})
}, [bar])
The effects are executed in the given order and only the "foo" from the last effect setter will be visible in the UI. For instance, the following component will output bar - 1:
const Component = ({ bar }) => {
const [foo, setFoo] = useState(undefined);
console.log("render", bar, foo);
useEffect(() => {
console.log("effect 1");
let someFoo = bar + 1;
setFoo(someFoo);
}, [bar]);
useEffect(() => {
console.log("effect 2");
let anotherFoo = bar - 1;
setFoo(anotherFoo);
}, [bar]);
return (
<div>
{bar} sets {foo}
</div>
);
};
https://codesandbox.io/s/proud-leaf-g55n9?file=/src/App.js:76-482
EDIT: If you use [] as the dependency array, it will only execute once. If there's an async function inside the effect like fetch, the last executed setFoo will prevail. The following example will display random results in each click:
useEffect(() => {
if (disabled) {
const random = 500 * Math.random();
const handle = setTimeout(() => {
setFoo(1);
}, random);
return () => clearTimeout(handle);
}
}, [disabled]);
useEffect(() => {
if (disabled) {
const random = 500 * Math.random();
const handle = setTimeout(() => {
setFoo(2);
}, random);
return () => clearTimeout(handle);
}
}, [disabled]);
Example 2:
https://codesandbox.io/s/jovial-architecture-0ybcz?file=/src/App.js
From what I can tell, the timer being called in a different scope.. how can I accomplish a function to stop the timer? Going a bit crazy here, thank you for any help.
const SomeComponent = ({ isPlaying }) => {
let timer = null;
React.useEffect(() => {
if (isPlaying) {
startTimer();
}
},[isPlaying]);
const startTimer = () => {
timer = setInterval(() => {
console.log('tick');
}, 1000);
};
const stopTimer = () => {
console.log('stopping timer: ', timer); // shows null, instead of giving the timerId to stop properly
clearInterval(timer);
};
The timer variable will "reset" each time your component is re-rendered. Even if it holds your timer, a re-render will set its value to null again.
You could either move out of the component scope, or use useRef to keep the variable through re-renders:
const SomeComponent = ({ isPlaying }) => {
const timer = React.useRef(null);
React.useEffect(() => {
if (isPlaying) {
startTimer();
}
return () => clearInterval(timer.current);
}, [isPlaying]);
const startTimer = () => {
timer.current = setInterval(() => {
console.log('tick');
}, 1000);
};
const stopTimer = () => {
clearInterval(timer.current);
};
Note that I also force a clearInterval by using a return inside the useEffect. This way the component will automatically "clean up" when it unmounts. I also changed timer to be a constant.