redux-requests
redux-requests copied to clipboard
Support short polling
Sometimes it might be handy to refresh query data, which could allow to have more "real-time" user experience. This is how I would go about it:
- To
RequestAction
addmeta.pool: number
(in seconds), this would be an interval, how often queries should be refetched automatically - We could implement it as a separate middleware (probably it would go even as 1st one in the stack)
- Of course this middleware should be disabled when running with
ssr: 'server'
-
PollingMiddleware
would just pass all actions through, but also it would check for queries withmeta.poll
, for which it would activatesetInterval
, repeating those actions, dispatching them again afterx
seconds - I think that dispatched query from this new middleware could also add
meta.pollCounter: number
, it could show us which time a given query is repeated, and also this could prevent poll looping - we shouldn't create interval again for a query which is result of this middleware, otherwise we would have multiple intervals for the same query - Making a new request of the same type (type + requestKey) without
meta.pollCounter
should clear current interval (it really means than a new query was dispatched, not from middleware but outside), and, depending onmeta.poll
, possibly set new interval - we should add
stopPolling
action which should have the same arguments like for exampleabortRequests
- probably new flag should be added to
resetActions
but for now I won't provide details asresetActions
is work in progress onpure-react
branch
That would be really amazing. My current usage pattern;
@favger Thx for the snippet. I can see you use React, so you might be interested that additionally stopPolling
callback will be returned by useQuery
, also unmounting useQuery
, changing type
or requestKey
will reset query, and resetting will also stop polling if any
@iwan-uschka @favger the only thing I am wondering, should we only have meta.poll
to fire request which will be auto polled, or maybe we should also have a dedicated action which starts polling for an already present query? Personally I am really into this assymetry, starting just with dispatching query with meta.poll
(no startPolling
action), and stopping either by:
- resetting
-
stopPolling
action dispatching - firing the very same query again, as point 6 in the task requirements.
I don't wanna introduce startPolling
action, because this is not as simple, usually we would need variables to request action, and I don't wanna store them for future usage like startPolling
action.
Theoretically though, we could introduce startPolling
callback in useQuery
, under the hood it would just dispatch query with passed variables
(this will be new prop in the future release in useQuery
) and added meta.poll
automatically. However, I am not sure it is worth doing.
@klis87 I cannot think of any use case right now where startPolling
callback would be needed. We have total control when to dispatch the specific action. If i need the same request type separately as a one-time action and a loop action i can create 2 actions separately or dispatch the same action using different parameters.
In this case i think we don't even need any stopPolling
callback. We should just use abortRequests
(@redux-requests/core
) as before. Or do you think it's necessary to abort manually a single request execution within a loop without aborting the loop itself?
I had to create an async loop mechanism this year myself. I just created a gist you can look at. I am using an iterator function and we can configure the behaviour like this
// config.interval.type
// - strict: cancel previous run if necessary after timeout (interval duration) and start a new one
// - placid: wait for previous run to complete and start timeout (interval duration) afterwards
// - efficient: wait for previous run to complete and wait for rest of timeout (interval duration) if necessary
Perhaps for inspiration :)
And @favger: checking isWindowActive
is a smart thing to do during polling. Thanks for sharing. I'll take this into account for my next projects. Just one comment: you could also run clearInterval
on isWindowActive===false
. This way no data is fetched in the background.
@klis87 I trust your feelings about this. My ability to analyze is not very good.
Maybe the SWR's settings can help us get an idea.
Good work
@iwan-uschka Thx for the feedback. Regarding abortRequest, I prefer to keep this only to just abort requests, no matter whether polling is used or not. Hence I think dedicated action stopPolling would be cool. However, resetRequests should stop all polling, I cannot think about any case which would reset a request just to make another with polling. And thanks for the gist :)
@favger actually very cool idea with isWindow active, we could have actions like pausePolling and resumePolling! This is probably nice to have TODO, for the future, but it makes sense not to pool when sth is not visible!
Just in case, if anyone started working on this, please let me know, as probably this is the next on my list, as I just published new version focused on React. If not, that's totally fine, I just don't want to duplicate our efforts :)
@iwan-uschka @favger btw, I am interested on your opinion about https://github.com/klis87/redux-requests/issues/432 , I would really appreciate your feedback!
@iwan-uschka @favger released in https://github.com/klis87/redux-requests/releases/tag/%40redux-requests%2Fcore%401.5.0
note that I didn't implement pause and resume polling actions, because it can be just achieved by dispatching stopPolling
and then, in a proper time by dispatching a given query with meta.poll
again
@klis87 I tested it, it works fine. How would you recommend not to stop automatic renewal, except for the browser tab only?
@favger you mean to stop polling when a browser tab is not active?
@klis87 Yes, definitely
@favger probably you would need to use onEffect
in your react component, listen to tab window events and:
- dispatch
stopPolling
fromuseQuery
response on disactivating tab - dispatch
load
fromuseQuery
response on activating tab
if this works, we could think of an extra prop like disablePolling: boolean
for useQuery
, and even for RequestsProvider
- then we could set it globally
@klis87 I already use a similar structure as you said, but having a shorter method may be more comfortable for the developer.
Thank you very much for your support. Good work.
@favger Thank you as well for contributing with valid issues and ideas!
Once you set this up, please share some snippet, so I will make this easier to support!
We can solve it this way;
/*
* Action Types
**/
export const SET_WINDOW_ACTIVE = "APP WINDOW ACTIVE STATE";
/*
* Action Creators
**/
export const setWindowActive = state => ({
type: SET_WINDOW_ACTIVE,
payload: state
});
/*
* Action Reducers
**/
case SET_WINDOW_ACTIVE: {
const isWindowActive = action.payload === "toggle" ? !state.isWindowActive : action.payload;
return {
...state,
isWindowActive
}
}
// [WINDOW]: Focus (Event)
useEffect(() => {
if (document.hidden) dispatch(setWindowActive(false));
const onWindowCheckFocus = (e) => e.type === "visibilitychange" && dispatch(setWindowActive("toggle"));
document.addEventListener("visibilitychange", onWindowCheckFocus);
return () => document.removeEventListener("visibilitychange", onWindowCheckFocus);
}, [setWindowActive]);
meta.poll= 10 meta.pollWhenHidden = true/false
- if pollWhenHidden is false, the poll will not work while the window is invisible.
I hope that has been revealing.
Maybe we can show it that way.
@favger ok, so you have a global way to get this attr. So, I guess extra prop to useQuery
like pausePolling={!isWindowActive}
would do it. If this is true, then:
-
useQuery
would removepoll
frommeta
on mount -
useQuery
would firestopPolling
whenpausePolling
is switched fromfalse
totrue
-
useQuery
would refetch (ifautoLoad
istrue
whenpausePolling
is switched fromtrue
tofalse
), without strippingmeta.poll
ofc
Adding the same in RequestsProvider
would be also cool. The only issue is that you store this value in store, which can be created by RequestsProvider
itself. Then probably we would need to use a callback for provider pausePolling
prop.
Perhaps this actually should be solved on Redux layer (in the core)...
For now I am pretty sure you could create useQuery
wrapper with above logic for code reusability
@klis87 excellent
Hope it's not too late to join the party. Installed & tested [email protected]
. Works great 👍. But there is a "but" :)
I want to make a suggestion for an improvement because as a developer i probably want to handle requests differently depending on the use case and respecting potentially network issues. Before giving some examples i would like to give a quick reminder of the configuration matrix i mentioned at the beginning of this thread:
// config.interval.type
// - strict: cancel previous run if necessary after timeout (interval duration) and start a new one
// - placid: wait for previous run to complete and start timeout (interval duration) afterwards
// - efficient: wait for previous run to complete and wait for rest of timeout (interval duration) if necessary
@klis87 , you implemented the strict
option. A use case i can think of could be something like poll: 30
. If a request hasn't completed after 30 seconds it can be treated as a timeout without any doubt. So it can be killed and a new request can be started.
Another example: short timed polling, for instance poll: 2
. User has to deal with bad network conditions. The request takes more than 2 seconds. Hypothetically, i know :) In this case the user won't get any response because the request is killed before a response can be received completely. In this case i would prefer to use the option i call placid
or efficient
. A running request should not be killed (using axios
you can define a timeout
additionally which will kill the request no matter what). The app should wait for the response before starting a new request.
@klis87 , if you like i could come up with a proposal or pull request including these options. We can discuss about the naming of course, or if placid
is needed next to efficient
or the other way around. Only having the strict
option like it is implemented right now, i couldn't use poll
mechanism unfortunately.
@iwan-uschka I don't have enough time to analyze your post tonight long enough unfortunately, I will try to do it tomorrow, but I will answer now as you mentioned very important topic which I feel is related to sth else and will be compatible with your suggestion. See https://github.com/klis87/redux-requests/issues/416 for my effects proposal, for example with takeLeading
effect if meta.poll is 10, but previous request is still pending, then new one would just join the previous one :) but perhaps polling should have dedicated options like you said, no matter what effect is set. But I sense that effects would be enough here as well.
Anyway, right now I am working on subscriptions, which probably will be used more often than polling anyway for real-time functionality.
@iwan-uschka I analyzed your interval type suggestion and I think that:
-
strict
is the same astakeLatest
- we already have it -
efficient
is the same astakeLeading
-
placid
- is very similar toefficient
, it would also usetakeLeading
, but timeout is shifted depending on the response time of a previous request
Is it necessary to support both efficient
and placid
?