Improved cache invalidation, and minor design fixes

This commit is contained in:
giancarlo
2024-06-03 15:10:40 +07:00
parent d0c6981e78
commit 88cebc2126
10 changed files with 7655 additions and 9714 deletions

View File

@@ -0,0 +1,24 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// set gcTime to 0 on the server
// as we cannot invalidate the cache on the server
const isServer = typeof document === 'undefined';
const gcTime = isServer ? 0 : undefined;
const staleTime = isServer ? 0 : 60 * 1000;
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime,
staleTime,
},
},
});
export function ReactQueryProvider(props: React.PropsWithChildren) {
return (
<QueryClientProvider client={queryClient}>
{props.children}
</QueryClientProvider>
);
}

View File

@@ -2,7 +2,6 @@
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'; import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental';
import { ThemeProvider } from 'next-themes'; import { ThemeProvider } from 'next-themes';
@@ -17,8 +16,9 @@ import pathsConfig from '~/config/paths.config';
import { i18nResolver } from '~/lib/i18n/i18n.resolver'; import { i18nResolver } from '~/lib/i18n/i18n.resolver';
import { getI18nSettings } from '~/lib/i18n/i18n.settings'; import { getI18nSettings } from '~/lib/i18n/i18n.settings';
import { ReactQueryProvider } from './react-query-provider';
const captchaSiteKey = authConfig.captchaTokenSiteKey; const captchaSiteKey = authConfig.captchaTokenSiteKey;
const queryClient = new QueryClient();
const CaptchaTokenSetter = dynamic(async () => { const CaptchaTokenSetter = dynamic(async () => {
if (!captchaSiteKey) { if (!captchaSiteKey) {
@@ -44,7 +44,7 @@ export function RootProviders({
return ( return (
<MonitoringProvider> <MonitoringProvider>
<QueryClientProvider client={queryClient}> <ReactQueryProvider>
<ReactQueryStreamedHydration> <ReactQueryStreamedHydration>
<I18nProvider settings={i18nSettings} resolver={i18nResolver}> <I18nProvider settings={i18nSettings} resolver={i18nResolver}>
<CaptchaProvider> <CaptchaProvider>
@@ -64,7 +64,7 @@ export function RootProviders({
</CaptchaProvider> </CaptchaProvider>
</I18nProvider> </I18nProvider>
</ReactQueryStreamedHydration> </ReactQueryStreamedHydration>
</QueryClientProvider> </ReactQueryProvider>
</MonitoringProvider> </MonitoringProvider>
); );
} }

View File

@@ -69,7 +69,7 @@ export function PersonalAccountDropdown({
const phone = user?.phone ?? undefined; const phone = user?.phone ?? undefined;
return email ?? phone; return email ?? phone;
}, [user?.email, user?.phone]); }, [user]);
const displayName = const displayName =
personalAccountData?.name ?? account?.name ?? user?.email ?? ''; personalAccountData?.name ?? account?.name ?? user?.email ?? '';

View File

@@ -140,6 +140,7 @@ function useSetSession(tokens: { accessToken: string; refreshToken: string }) {
return useQuery({ return useQuery({
queryKey: ['impersonate-user', tokens.accessToken, tokens.refreshToken], queryKey: ['impersonate-user', tokens.accessToken, tokens.refreshToken],
gcTime: 0,
queryFn: async () => { queryFn: async () => {
await supabase.auth.setSession({ await supabase.auth.setSession({
refresh_token: tokens.refreshToken, refresh_token: tokens.refreshToken,

View File

@@ -33,6 +33,7 @@
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"next": "14.2.3", "next": "14.2.3",
"react": "18.3.1", "react": "18.3.1",
"server-only": "^0.0.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"eslintConfig": { "eslintConfig": {

View File

@@ -4,6 +4,8 @@ import { useEffect } from 'react';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import { useQueryClient } from '@tanstack/react-query';
import { useSupabase } from './use-supabase'; import { useSupabase } from './use-supabase';
import { useRevalidateUserSession, useUserSession } from './use-user-session'; import { useRevalidateUserSession, useUserSession } from './use-user-session';
@@ -30,11 +32,12 @@ export function useAuthChangeListener({
const router = useRouter(); const router = useRouter();
const revalidateUserSession = useRevalidateUserSession(); const revalidateUserSession = useRevalidateUserSession();
const session = useUserSession(); const session = useUserSession();
const queryClient = useQueryClient();
const accessToken = session.data?.access_token; const accessToken = session.data?.access_token;
useEffect(() => { useEffect(() => {
// keep this running for the whole session unless the component was unmounted // keep this running for the whole session unless the component was unmounted
const listener = client.auth.onAuthStateChange((event, user) => { const listener = client.auth.onAuthStateChange(async (event, user) => {
// log user out if user is falsy // log user out if user is falsy
// and if the current path is a private route // and if the current path is a private route
const shouldRedirectUser = const shouldRedirectUser =
@@ -47,10 +50,14 @@ export function useAuthChangeListener({
return; return;
} }
// revalidate user session when user signs in or out
if (event === 'SIGNED_OUT') { if (event === 'SIGNED_OUT') {
await queryClient.invalidateQueries();
return router.refresh(); return router.refresh();
} }
// revalidate user session when access token is out of sync
if (accessToken) { if (accessToken) {
const isOutOfSync = user?.access_token !== accessToken; const isOutOfSync = user?.access_token !== accessToken;
@@ -70,6 +77,7 @@ export function useAuthChangeListener({
pathName, pathName,
appHomePath, appHomePath,
privatePathPrefixes, privatePathPrefixes,
queryClient,
]); ]);
} }

View File

@@ -1,13 +1,15 @@
import { useMutation } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useSupabase } from './use-supabase'; import { useSupabase } from './use-supabase';
export function useSignOut() { export function useSignOut() {
const client = useSupabase(); const client = useSupabase();
const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async () => { mutationFn: async () => {
await client.auth.signOut(); await client.auth.signOut();
await queryClient.invalidateQueries();
}, },
}); });
} }

View File

@@ -24,14 +24,10 @@ export function useUser(initialData?: User | null) {
return Promise.reject('Unexpected result format'); return Promise.reject('Unexpected result format');
}; };
// update staleTime to 5 minutes
const staleTime = 1000 * 60 * 5;
return useQuery({ return useQuery({
queryFn, queryFn,
queryKey, queryKey,
initialData, initialData,
staleTime,
refetchInterval: false, refetchInterval: false,
refetchOnMount: false, refetchOnMount: false,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,

View File

@@ -92,7 +92,7 @@ function getClassNameBuilder() {
}, },
selected: { selected: {
true: '', true: '',
false: '', false: 'hidden sm:flex',
}, },
complete: { complete: {
true: '', true: '',

17313
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff