Reputation: 13
I'm playing with React Hooks for more than a few hours, I'm probably ran into an intriguing problem: using setInterval just doesn’t work as I'd expect with react-native
function Counter() {
const [time, setTime] = useState(0);
const r = useRef(null);
r.current = { time, setTime };
useEffect(() => {
const id = setInterval(() => {
console.log("called");
r.current.setTime(r.current.time + 1);
}, 1000);
return () => {
console.log("cleared");
clearInterval(id);
};
}, [time]);
return <Text>{time}</Text>;
}
The code above should clearInterval every time that time
state changes
It works fine on ReactJS
but on React-native
I'm getting an error says "Callback() it's not a function"
It's working as expected in Reactjs
https://codesandbox.io/s/z69z66kjyx
"dependencies": {
"react": "16.8.3",
"react-native": "^0.59.6",
...}
Update: I tried to use the ref like this example but still getting same error
const [time, setTime] = useState(0);
useInterval(() => {
setTime(time +1);
});
return (<Text>{time}</Text>);
}
function useInterval(callback) {
const savedCallback = useRef();
// Remember the latest function.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
let id = setInterval(()=>savedCallback.current(), delay);
return () => clearInterval(id);
});
}
Upvotes: 1
Views: 933
Reputation: 6743
Because you are mutating the DOM via a DOM node refs and the DOM mutation will change the appearance of the DOM node between the time that's rendered and your effects mutates it. then you don't need to use useEffect
you will want to use useLayoutEffect
useLayoutEffect
this runs synchronously immediately after React has performed all the DOM mutations.
import React, {useState, useLayoutEffect,useRef} from 'react';
import { Text} from 'react-native';
const [time, setTime] = useState(0);
useInterval(() => {
setTime(time +1);
});
return (<Text>{time}</Text>);
}
function useInterval(callback) {
const savedCallback = useRef();
// Remember the latest function.
useLayoutEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useLayoutEffect(() => {
let id = setInterval(()=>{
console.log('called');
return savedCallback.current();
}, delay);
return () => {
console.log('cleared');
return clearInterval(id);
}
});
}
if you just using useEffect
and getting this error
Uncaught TypeError: callback is not a function at flushFirstCallback (scheduler.development.js:348) at flushWork (scheduler.development.js:441) at MessagePort.channel.port1.onmessage (scheduler.development.js:188)
This is a bug in RN because of wrong scheduler
version, Unfortunately RN didn't have an explicit dependency on scheduler version by mistak. Dan Abramov already fixed this bug on scheduler version "0.14.0"
To solve the problem just run the following command
npm install [email protected] --save
Or Try adding "scheduler": "0.14.0"
to your package.json
in dependencies
and re-running your package manager
Upvotes: 2
Reputation: 202686
You should be able to still use the state hook variables in the effect hook as they are in scope.
useRef: mutations here aren't tracked, so they don't trigger re-renders.
I feel that using refs in the way you're trying to is more verbose than just using the state and setter directly. The useRef
ref is intended for mutable values over time, but you already get that with the useState
hook. The ref works because you're not really mutating the ref, but instead are simply overwriting it each render cycle with the contents of the useState
hook that are updated.
I've updated my sandbox to use useRef
as the way you have, your useEffect
hook was causing your cleanup function to fire on every render, so removed the dependency. You'll notice now you only see "called" until you refresh.
Upvotes: 0