I've a hard time understanding how setInterval
works. My main problem is that an interval is re-initialized too often.
Basically, I want a context-sensitive sidebar to be modfied by MainElement
, and I want this sidebar to do something at a regular base. In the real scenario there the timer gets cancelled when unmounting ofc.
import { useEffect, useState } from 'react';
// This is the component called from outside
export const MainLayout = () => {
const [element2Content, setElement2Content] = useState<string | null>(null);
return (
<>
<MainElement setElement2Content={setElement2Content}>
Element1
</MainElement>
{element2Content && <Sidebar content={element2Content} />}
</>
);
};
// This component manipulates the sidebar via useEffect
const MainElement: React.FC<{ setElement2Content: (input: string) => void }> =
({ setElement2Content, children }) => {
useEffect(() => setElement2Content('content set from element 1'), []);
return <div>{children}</div>;
};
// This component utilizes the setInterval, but doesn't work as expected
const Sidebar: React.FC<{ content: string }> = ({ content }) => {
const [calls, setCalls] = useState(0);
useEffect(() => {
setInterval(() => {
console.log('interval called for', calls 1, 'times');
setCalls(calls 1);
}, 1000);
}, []);
return <div>{`content${content}, calls: ${calls}`}</div>;
};
The log is just interval called for 1 times
in a loop.
In the browser I see the components rendered, and I see interval called for 0 times
being changed to interval called for 1 times
, where it stops.
So I'm wondering: Why does it stop at 1
? It seems like setInterval
gets reset all the time.
To understand the behavior of a timer a bit more, I changed my MainElement
to
const MainElement: React.FC<{ setElement2Content: (input: string) => void }> =
({ setElement2Content, children }) => {
useEffect(() => setElement2Content('content set in element 1'), []);
useEffect(() => {
setInterval(() => {
console.log('interval called from mainelement');
}, 1000);
}, []);
return <div>{children}</div>;
};
Now, for some reason the MainElement
is also re-rendered repeatedly, and so is the sidebar. The console logs
interval called in mainelement
interval called for 1 times
I'ld be grateful for any ideas or explanations!
CodePudding user response:
The interval is running correctly, but in your interval function calls
is never being updated passed 1. This is because the calls
variable in the setInterval
function is stale (i.e. it is always equal to 0 when the function is invoked, so setCalls(call 1)
will always update calls
to 1.) This staleness is because the calls
variable in the function is captured in the closure formed when the effect is run during the first render of the component and never updated thereafter.
A quick way to address this is to use the functional version of the setState call:
useEffect(() => {
setInterval(() => {
console.log('interval called for', calls 1, 'times');
setCalls(prevCalls => prevCalls 1);
}, 1000);
}, []);