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

fix(focusManager): stop listening for focus events #4805

Merged
merged 1 commit into from
Jan 13, 2023

Conversation

ilyasmez
Copy link

Why?

As discussed in #4797, focus event has many caveats, as it's triggered in the following scenarios:

  • When interacting with an iframe.
  • When opening/closing a browser dialog (alert, confirm).
  • When moving between the page and DevTools.
  • When opening/closing a file picker.

I think the original idea behind refetchOnWindowFocus was to refetch the queries when the user "comes back" to the page, and that's achievable using visibilitychange.

Before

You can see that the query is being refetched in many unexpected scenarios, while the user didn't leave the page.

rq-focus-fix-before.1.mp4

After

The query is refetched only when the user stop using the app, and comes back.

rq-focus-fix-after.1.mp4

What does this PR do

  • Remove the focus event listener in focusManager.
  • Update docs (also removed the old pitfalls and caveats, as they will be fixed with this change).
  • Update tests (I went with consistency instead of trying to rewrite some of them).

The `focus` event had many caveats (as discussed in TanStack#4797), so we now listen only for `visibilitychange` event.
@codesandbox-ci
Copy link

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 52f4cda:

Sandbox Source
@tanstack/query-example-react-basic-typescript Configuration
@tanstack/query-example-solid-basic-typescript Configuration
@tanstack/query-example-vue-basic Configuration

Comment on lines -18 to -19
If you see a refetch that you are not expecting, it is likely because you just focused the window and TanStack Query is doing a [`refetchOnWindowFocus`](../guides/window-focus-refetching). During development, this will probably be triggered more frequently, especially because focusing between the Browser DevTools and your app will also cause a fetch, so be aware of that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow this is great! I didn't know that it would also fix this issue ❤️

Comment on lines -108 to +91
[//]: # 'Example5'
[//]: # 'Example4'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should switch to named examples @DamianOsipiuk 😅. I see the vue-query docs only overwrite/use the 'Example' slot, but this could become tedious with numbers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you know naming things is hard, so i just went with numbers 🙈
But yeah, probably we should name them properly.

Copy link
Collaborator

@TkDodo TkDodo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accidentally pressed request changes - i wanted to approve 😅

@TkDodo TkDodo merged commit 324e69c into TanStack:v5 Jan 13, 2023
@DamianOsipiuk
Copy link
Contributor

@TkDodo I believe we should add a note about this to the migration guide.

@TkDodo
Copy link
Collaborator

TkDodo commented Jan 13, 2023

aah yes, forgot about that 👍 .
I'll do it on the v5 branch directly

@lbhbrave
Copy link

Hello guys. We have also encountered ux experience issues caused by focus recently and would like to know if this pull request can be merged into version V3. The risk is too high for us to perform a major version upgrade

@TkDodo
Copy link
Collaborator

TkDodo commented Jul 15, 2023

if this pull request can be merged into version V3

it's a breaking change so no. also, it was merged to v5. seems like you haven't upgraded in over a year ...

@lbhbrave
Copy link

lbhbrave commented Aug 23, 2023 via email

@gabrielbrattgard
Copy link

@TkDodo @ilyasmez I might be wrong here but this only seems to work when switching between tabs and NOT between browser windows or returning from another desktop application. Is that intentional?

@TkDodo
Copy link
Collaborator

TkDodo commented Nov 3, 2023

@gabrielbrattgard it does whatever the browser visibilitychange does so yes, that's an purpose. You can totally opt back into the focus event though with focusManager.setEventListener

@mrlubos
Copy link

mrlubos commented Dec 14, 2023

This is great @ilyasmez!

@RiccardoRomagnoli
Copy link

@ilyasmez @TkDodo There is a case where without focus we can not archive the intended outcome.

With this change, now if you have two browser windows (one of them is our app), not maximised and you switch between one and the other (by click, by tab, eetc) the query will now not refetch anymore.

@ilyasmez
Copy link
Author

ilyasmez commented Feb 22, 2024

There is a case where without focus we can not archive the intended outcome.

With this change, now if you have two browser windows (one of them is our app), not maximised and you switch between one and the other (by click, by tab, eetc) the query will now not refetch anymore.

@RiccardoRomagnoli, do you really want the page to refetch when the user interacts with it? This might result in content changing under their cursors (e.g. the item they wanted to click is not there anymore, or the position of the item changes). I believe that interacting with the page is already too late.
What you need is probably the broadcastQueryClient, it will update all the open windows immediately as mutations happen, which is a much superior user experience than waiting for the user to interact with the page, imo.

@HemalR
Copy link

HemalR commented Aug 27, 2024

There is a case where without focus we can not archive the intended outcome.
With this change, now if you have two browser windows (one of them is our app), not maximised and you switch between one and the other (by click, by tab, eetc) the query will now not refetch anymore.

@RiccardoRomagnoli, do you really want the page to refetch when the user interacts with it? This might result in content changing under their cursors (e.g. the item they wanted to click is not there anymore, or the position of the item changes). I believe that interacting with the page is already too late. What you need is probably the broadcastQueryClient, it will update all the open windows immediately as mutations happen, which is a much superior user experience than waiting for the user to interact with the page, imo.

This behaviour can be desirable in certain scenarios. E.g. A notification badge counter that can update when a user refocuses on to the browser window.

My workaround fix from the comments on this thread plus experimenting with other ways:

<script lang="ts">
	import { browser } from '$app/environment';
	import { QueryClient, QueryClientProvider, focusManager } from '@tanstack/svelte-query';
	import { onMount } from 'svelte';
	import '../app.css';

	let { children } = $props();

	const queryClient = new QueryClient({
		defaultOptions: {
			queries: {
				enabled: browser,
				refetchOnWindowFocus: true,
				staleTime: 0 // Set this to 0 to always consider data stale
			}
		}
	});

	onMount(() => {
		focusManager.setEventListener((handleFocus) => {
			const focus = () => {
                                 if (!document.hidden) {
				  console.log('Focus from parent');
				  handleFocus(true);
				  queryClient.refetchQueries(); // Without this, the above 'focus from parent' will be logged but queries will only refetch the first time the window is refocused from a different program. After that, data will not be refetched.
                                 }
			};
			window.addEventListener('visibilitychange', focus);
			window.addEventListener('focus', focus);
			return () => {
				window.removeEventListener('visibilitychange', focus);
				window.removeEventListener('focus', focus);
			};
		});
	});
</script>

<QueryClientProvider client={queryClient}>
	{@render children()}
</QueryClientProvider>

If anybody can explain the weird reason why the queries only refetch if manually triggered I'd love to know and improve what I'm doing here.

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 this pull request may close these issues.

8 participants