TanStackT
TanStack2y ago
10 replies
dry-scarlet

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

  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,
})


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
}

as you can see the context does not update before beforeLoad is checked again thanks to router.invalidate
Was this page helpful?