I get this:
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.
in Camera (created by App)
The useEffect
doesn't have subscriptions in itself.
But, it calls startRaf()
from useRaf
which creates a subscription. However, it comes with a cleanup function!
To reproduce:
You should see the warning above in the console.
What am I doing wrong in my hooks?
Every time you call useRaf
you replace your unmount handler with one that references a different version of rafID
.
Try instead with refs,
export default function useRaf() {
let [counter, setCounter] = useState(0);
let rafID = useRef(null);
function loop() {
setCounter(counter => counter + 1);
rafID.current = requestAnimationFrame(loop);
}
function start() {
rafID.current = requestAnimationFrame(loop);
}
useEffect(() => {
return () => {
cancelAnimationFrame(rafID.current);
};
}, []);
return [counter, start];
}
@jacobp100 I think this solution may not work perfectly, though. As cancelAnimationFrame
method is executed in the clean-up period, the loop
method continues so a new rafID.current
will be assigned, even though you already canvel previous rafID.current
.
And I don't think we need to use useRef
here, a normal variable can already handle the storage of requestAnimationFrame ID? Change let rafID = useRef(null);
to let rafID = null;
.
A safe way to guarantee current animation's stop is to add conditions in your main loop method, loop
method can be rewrited to:
function loop() {
setCounter(counter => counter + 1);
rafID.current = YOUR_CANCEL_CONDITION ?
cancelAnimationFrame(rafID.current)
:
ArequestAnimationFrame(loop);
}
in which YOUR_CANCEL_CONDITION is the logic you should handle with.
Well, I found the problem of my code. It's because I mistakenly use useEffect
, and I should use useLayoutEffect
instead when I want to use requestAnimationFrame
API.
In react docs, there's a tip about this, and I think your code @jacobp100 may face the same problem:
Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen. This makes your app feel more responsive. The majority of effects don’t need to happen synchronously. In the uncommon cases where they do (such as measuring the layout), there is a separate useLayoutEffect Hook with an API identical to useEffect.
:-)
Most helpful comment
Well, I found the problem of my code. It's because I mistakenly use
useEffect
, and I should useuseLayoutEffect
instead when I want to userequestAnimationFrame
API.In react docs, there's a tip about this, and I think your code @jacobp100 may face the same problem:
:-)