Auth / beforeLoad / router.invalidate / redirect problem with router context update
i have the problem probably due to my understanding of data flow in router context?
i have simple
to trigger beforeLoad after updating react query cache
and this is my authProvider
as you can see the context does not update before beforeLoad is checked again thanks to router.invalidate
i have simple
async function onSubmit(data: z.infer<typeof FormSchema>) {
await auth.login(data).then(async () => {
await router.invalidate()
})
} async function onSubmit(data: z.infer<typeof FormSchema>) {
await auth.login(data).then(async () => {
await router.invalidate()
})
}to trigger beforeLoad after updating react query cache
import { createFileRoute, redirect } from '@tanstack/react-router'
import { z } from 'zod'
const fallback = '/' as const
export const Route = createFileRoute('/_auth')({
validateSearch: z.object({
redirect: z.string().optional().catch(''),
}),
beforeLoad: async ({ context, search }) => {
if (context.auth.isAuthenticated) {
throw redirect({ to: search.redirect || fallback })
}
},
staleTime: 0,
preloadStaleTime: 0,
})import { createFileRoute, redirect } from '@tanstack/react-router'
import { z } from 'zod'
const fallback = '/' as const
export const Route = createFileRoute('/_auth')({
validateSearch: z.object({
redirect: z.string().optional().catch(''),
}),
beforeLoad: async ({ context, search }) => {
if (context.auth.isAuthenticated) {
throw redirect({ to: search.redirect || fallback })
}
},
staleTime: 0,
preloadStaleTime: 0,
})and this is my authProvider
import axios from '@/utils/axios'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import React from 'react'
export type Auth = {
user: { email: string }
isAuthenticated: boolean
login: (user: { email: string }) => Promise<void>
logout: () => Promise<void>
isLoading: boolean
}
const AuthContext = React.createContext<Auth | null>(null)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (user: { email: string }) =>
axios.post('auth/login', user).then((res) => res.data),
onSuccess: (data) => {
queryClient.setQueryData(['user'], data)
return data
},
})
const { data: user, isLoading } = useQuery({
queryKey: ['user'],
queryFn: async () => await axios.get('api/v1/user').then((res) => res.data),
retry: false,
})
const isAuthenticated = !!user
const login = async (user: { email: string }) => {
await mutation.mutateAsync(user).then(async (data) => {
// await queryClient.invalidateQueries({ queryKey: ['user'] })
await queryClient.setQueryData(['user'], data)
})
}
console.log('user', user)
const logout = async () => {
await axios
.post('auth/logout')
.then(async () => {
await queryClient.setQueryData(['user'], null)
// await queryClient.invalidateQueries({ queryKey: ['user'] })
})
.catch(async () => {
await queryClient.setQueryData(['user'], null)
// await queryClient.invalidateQueries({ queryKey: ['user'] })
})
}
return (
<AuthContext.Provider
value={{ isAuthenticated, user, login, logout, isLoading }}
>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = React.useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}import axios from '@/utils/axios'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import React from 'react'
export type Auth = {
user: { email: string }
isAuthenticated: boolean
login: (user: { email: string }) => Promise<void>
logout: () => Promise<void>
isLoading: boolean
}
const AuthContext = React.createContext<Auth | null>(null)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (user: { email: string }) =>
axios.post('auth/login', user).then((res) => res.data),
onSuccess: (data) => {
queryClient.setQueryData(['user'], data)
return data
},
})
const { data: user, isLoading } = useQuery({
queryKey: ['user'],
queryFn: async () => await axios.get('api/v1/user').then((res) => res.data),
retry: false,
})
const isAuthenticated = !!user
const login = async (user: { email: string }) => {
await mutation.mutateAsync(user).then(async (data) => {
// await queryClient.invalidateQueries({ queryKey: ['user'] })
await queryClient.setQueryData(['user'], data)
})
}
console.log('user', user)
const logout = async () => {
await axios
.post('auth/logout')
.then(async () => {
await queryClient.setQueryData(['user'], null)
// await queryClient.invalidateQueries({ queryKey: ['user'] })
})
.catch(async () => {
await queryClient.setQueryData(['user'], null)
// await queryClient.invalidateQueries({ queryKey: ['user'] })
})
}
return (
<AuthContext.Provider
value={{ isAuthenticated, user, login, logout, isLoading }}
>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = React.useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}as you can see the context does not update before beforeLoad is checked again thanks to router.invalidate