Reputation: 3665
I have a component that, when it is visible, I want to query an API every 10 seconds. When the component is closed / not visible, I want that polling to stop.
The problem I have is that the polling continues even after the component has been hidden. Whatever I am doing my code is not recognising the newly updated state variable.
const DisplayState = Object.freeze({
DisplayGroupNamesForSelection: 0,
DisplayLogStream: 1
});
export default function LogStream({ isOpen }) {
const [currentState, setCurrentState] = useState(DisplayState.DisplayGroupNamesForSelection);
const [logEvents, setlogEvents] = useState([]);
const onStartStreamingButtonClicked = async logGroupName => {
setCurrentState(DisplayState.DisplayLogStream);
pollForLogs(logGroupName);
};
const pollForLogs = async logGroupName => {
try {
const newLogList = await getLogs(logGroupName); // API call elsehwere
setlogEvents(newLogList);
} catch(e) {
//
} finally {
// If the component is still open, ping the API again in 10s
// However, this always evaluates to true
if(isOpen) {
setTimeout(() => {
pollForLogs(logGroupName);
}, 10000);
}
}
};
return (
<>
{!isOpen ? (
<></>
) : (
<div>
{(() => {
switch(currentState) {
case DisplayState.DisplayGroupNamesForSelection: {
return (
<LogStreamingGroupSelector
onStartStreamingButtonClicked={onStartStreamingButtonClicked}
/>
);
}
case DisplayState.DisplayLogStream: {
return (
<div>
{logEvents.map((log, i) => (
<sub key={log.timestamp}>{log.message}</sub>
))}
</div>
);
}
}
})()}
</div>
)}
</>
);
}
What I can do is a kinda' hacky solution where I declare a variable outside the component and have the continue polling depend on that:
let continuePollingForLogs = false;
export default function LogStream({ isOpen }) {
const [currentState, setCurrentState] = useState(DisplayState.DisplayGroupNamesForSelection);
const [logEvents, setlogEvents] = useState([]);
useEffect(() => {
continuePollingForLogs = isOpen;
}, [isOpen]);
...and then if I have the pollForLogs
function depend on the non-state variable continuePollingForLogs
, it does behave as intended.
I know something is going wrong here with how React handles state, but what exactly I cannot figure out. Even when I created independent useState
variables which I set within a useEffect
whenever isOpen
changed it still didn't work. The variable in pollForLogs()
's finally
was always true
.
Upvotes: 0
Views: 197
Reputation: 3665
This is the wrong approach when using hooks. I suggest any future reader read Dan Abramov's article and use his useInterval
custom hook:
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
Upvotes: 1
Reputation: 747
You could use the useRef
to create a mutable ref (variable) whose state (value) will be persisted throughout the component's lifecycle. Then, use the cleanup function in useEffect
to set it to false when component unmounts.
import React, { useState, useRef } from 'react';
export default function LogStream({ isOpen }) {
const [currentState, setCurrentState] = useState(
DisplayState.DisplayGroupNamesForSelection
);
const [logEvents, setlogEvents] = useState([]);
const continuePollingForLogs = useRef(isOpen);
useEffect(() => {
return () => {
// set ref to false when component unmounts.
continuePollingForLogs.current = false;
};
});
}
Upvotes: 2