Polling continues after component dismount
Hello,
Summary
I have a standard shared endpoint setup and use the injectEndpoint for configuring my endpoints and sorting them into neat directories.
Problem When polling an endpoint via RTK Query. The endpoint will still poll (one last time) after the component has unmounted.
Example
- I have a small application with two primary paths, using
react-router. - The
<Session />component will run a "heartbeat" query every 30 seconds. Additionally, I have attached two timers to ensure that the component successfully dismounts. - When I change path to a route that is under
<Public />and not<Session />the polling time will still continue to run one last time, triggering the endpoint while on the wrong component causing unexpected state changes.
The endpoint:
import api from 'api'; // Aliased
import apiTags from './apiTags';
const globalSettingsApi = api.injectEndpoints({
endpoints: builder => ({
getHeartbeat: builder.query({
query: () => `heartbeat`,
}),
}),
});
export const { useGetHeartbeatQuery } = globalSettingsApi;
The store:
import api from 'api'; // Aliased
const combinedReducers = combineReducers({
[api.reducerPath]: api.reducer,
});
const middlewares = [];
middlewares.push(api.middleware);
export const store = configureStore({
reducer: combinedReducers,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: false,
immutableCheck: false,
}).concat(middlewares),
devTools: window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose,
});
The main:
import { Provider } from 'react-redux';
import { store } from 'Redux/store'; // alias from above store
import { Router } from './Router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<Router />
</Provider>
</React.StrictMode>,
);
Router:
export const routes = [
{
element: <Application />,
children: [
{
element: <Public />,
children: [
{ index: true, element: <IrrelevantElement /> },
],
},
{
element: <Session />, // <--- This is the one we are reproducing the bug in
children: [
{
path: 'activity',
element: <IrrelevantElement />, // This is a valid component
},
],
},
]
}
Session
import { Outlet } from 'react-router';
import { useGetHeartbeatQuery } from 'Api/globalSettings';
export default Session = () => {
useEffect(() => {
const interval = setInterval(() => {
console.log('component is still alive 1 second');
}, 1000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
const interval = setInterval(() => {
console.log('component is still alive 10 seconds');
}, 10000);
return () => clearInterval(interval);
}, []);
const heartBeat = useGetHeartbeatQuery(undefined, {
pollingInterval: 30000,
});
return (
<>
<Outlet />
</>
)
}
Expected behaviour The expectation is that the polling timer should be cleared when the component dismounts.
Work around The current workaround is to wrap the query in a useEffect and abort it on the return callback.
Comments I tried to see if this has already been reported but I could not find anything, apologies if I have done anything wrong, kindly advise in comments and I will update accordingly! Thank you!
This would be reasonably expected behavior, because the cache entry still exists for keepUnusedDataFor time after the last component unsubscribes.
I'd have to check what the expectations are for clearing polls specifically.
Would I be mistaken in thinking that a component dismount would indicate the developer's desire to no longer poll? At least, in terms of awareness I feel the developer would assume that the poll is discontinued.