Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to pause Polling when window Unfocused #2516

Closed
littlejon opened this issue Jul 15, 2022 · 7 comments · Fixed by #4055
Closed

Option to pause Polling when window Unfocused #2516

littlejon opened this issue Jul 15, 2022 · 7 comments · Fixed by #4055
Milestone

Comments

@littlejon
Copy link

Sorry if this is a silly question, but is it possible to pause polling on a query if the window loses focus.

I currently have a situation where i need to manually initiate a query due to some other bad code where i have used prefetch in a horrible hacky way to bypass cache timeout (it's a pagination/inifinate scroll issue that is first on my list for a refactor!!).

  useEffect(() => {
    let unsubscribe: () => void;

    if (id) {
      const result = dispatch(
        getChatHistory.initiate(
          { id, page: 1 },
          {
            subscribe: true,
            subscriptionOptions: {
              pollingInterval: 10000,
            },
          }
        )
      );

      unsubscribe = result.unsubscribe;
    }

    return () => {
      unsubscribe && unsubscribe();
    };
  }, [id, dispatch]);

If there is no "native" way to pause polling, I think it should be possible to create a listener for the focus/unfocus action dispatches and set some state value accordingly.

I have posted here instead of SO in case the functionality could be useful (please close/delete the issue if there is no merit in the idea and I will ask on SO instead).

@markerikson
Copy link
Collaborator

markerikson commented Jul 15, 2022

Hmm. I don't think there's a way to pause polling atm, but I'm also not familiar with that portion of the codebase.

actually, looking at the code...

        if (
          api.internalActions.updateSubscriptionOptions.match(action) ||
          api.internalActions.unsubscribeQueryResult.match(action)
        ) {
          updatePollingInterval(action.payload, mwApi)
        }

// later

      const lowestPollingInterval = findLowestPollingInterval(subscriptions)

      if (!Number.isFinite(lowestPollingInterval)) {
        cleanupPollForKey(queryCacheKey)
        return
      }

so maybe dispatching api.internalActions.updateSubscriptionOptions would work here?

@phryneas
Copy link
Member

That would get it out of sync with the hook. It's currently not supported, but a well thought-out PR would be welcome :)

@littlejon
Copy link
Author

I would love to be able to contribute in some way, but this one might be out of my wheelhouse. I have solved my issue in the short term by creating actions for the Focus events and then setting state in extraReducers.

const onFocus = createAction("__rtkq/focused");
const onFocusLost = createAction("__rtkq/unfocused");

I see these actions are exported here:

export const onFocus = /* @__PURE__ */ createAction('__rtkq/focused')

But I was unable to import them and use them in my project (NextJS).

VSCode is happy to import them like this

import {
  onFocus,
  onFocusLost,
} from "@reduxjs/toolkit/dist/query/core/setupListeners";

Typescript is happy with it but NextJS isn't. Running dev or build results in

Module not found: Can't resolve '@reduxjs/toolkit/dist/query/core/setupListeners' in 'C:\Code\project\store\features\messages'

While creating my own actions has worked, i'm not a fan of the idea long term as it depends on RTK not changing the ActionType at some point in the future.

Is it possible to get these Actions exported so they can be used by projects as needed?

Now this is something that I could create a PR for if you agree it is a worthy exercise :-).

Thanks for your help.

@markerikson markerikson added this to the 1.9.x milestone Jan 28, 2023
@philjones88
Copy link

Chiming in to say I have similar issues with the imports of onFocus and it not being liked, in this case by Vite.

Also pausing polling seems like a really good feature as browsers have said they aren't happy if apps keep doing it whilst unfocused.

@markerikson markerikson modified the milestones: 1.9.x, 2.x bugfixes Dec 6, 2023
@riqts
Copy link
Contributor

riqts commented Jan 7, 2024

I am interested in contributing here, but I want to clarify what the preferred behavior by the team is first. These are my initial thoughts on it, but I may be off the mark and missing something crucial about RTKQuery as this is my first real dive into the source.

Options considered:

specific option for polling only to check if the configSlice considers the document focused or not before sending a refetch:

If the intention is to maintain the actual polling hooks functionality and interval, but just to block any actual refetch happening while the window is unfocused. Then I think simply adding a check before the actual refetch is dispatched could act as a pause while keeping the poll synced with the hook.

I am unsure if the best way to pass this option would be through the createAPI definition or through the hook as it is only impacting polling in this implementation so I am just showing a snippet of the implementation idea rather than the actual option handling

 function startNextPoll({
    queryCacheKey
  }, api2) {
    const state = api2.getState()[reducerPath];
    const querySubState = state.queries[queryCacheKey];
    // added boolean that takes from the config slice already tracking when the window is focused or unfocused
    const isFocused = state.config.focused;
    const subscriptions = internalState.currentSubscriptions[queryCacheKey];
    if (!querySubState || querySubState.status === "uninitialized" /* uninitialized */)
      return;
    const lowestPollingInterval = findLowestPollingInterval(subscriptions);
    if (!Number.isFinite(lowestPollingInterval))
      return;
    const currentPoll = currentPolls[queryCacheKey];
    if (currentPoll?.timeout) {
      clearTimeout(currentPoll.timeout);
      currentPoll.timeout = void 0;
    }
    const nextPollTimestamp = Date.now() + lowestPollingInterval;
    const currentInterval = currentPolls[queryCacheKey] = {
      nextPollTimestamp,
      pollingInterval: lowestPollingInterval,
      timeout: setTimeout(() => {
        currentInterval.timeout = void 0;
        // Added conditional to not dispatch when unfocused
        if (isFocused) { 
          api2.dispatch(refetchQuery(querySubState, queryCacheKey));
        }
      }, lowestPollingInterval)
    };
  }

Similar concept to the first but probably requires more rewrite but might be safer to avoid unintended side-effects

Having the same code as above but instead of just ignoring the dispatch, it can be added to the functionality that refetches a query when onFocus happens, effectively pausing the actual fetching but keeping it in sync and having the data returned to the user still

Other option but not necessarily in this scope

Pass an option to all hooks to not refetch server requests when unfocused and can handle it at the executeEndpoint and cacheLifecycle level where the endpoint actually decides whether a server request is necessary

First time looking into RTKQ Code so very possible I'm missing something but thats my thoughts to begin

@phryneas
Copy link
Member

phryneas commented Jan 7, 2024

It might be worth noting that a similar option was added to Apollo Client recently in apollographql/apollo-client#11397, so I suggest also reading the discussion there.

@riqts
Copy link
Contributor

riqts commented Jan 8, 2024

Thanks, that was helpful read and I took a look at react-query's. That PR in apollo follows a very similar implementation to the first one listed above. Normal polling behaviour continues but the query doesn't dispatch if the flag is set either in the createApi declaration or in the hooks query options.

Only difference is I planned on using the already existing focus state from the configSlice, but I think that's inconsequential, unless that handler has some behaviour im unaware of.

I'll spin up a PR when I get up in the morning that aims at a query hook option and pollHandler logic and has some tests.

I haven't actually looked at what is involved in adding the option to createApi or the actual endpoint definition yet if we want to involve that in this feature too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants