T
TanStack2y ago
wise-white

__root and context

So this is my main.tsx
const router = createRouter({
routeTree,
defaultPendingComponent: () => (
<div className={`p-2 text-2xl`}>
todo: spinner loading
</div>
),

defaultErrorComponent: ({error}) => <ErrorComponent error={error}/>,
context: {
auth: undefined!, // We'll inject this when we render
},
defaultPreload: 'intent',
})

const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
})


declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}

function InnerApp() {

const auth = useAuth();
return <RouterProvider router={router} context={{auth}}/>
}

function App() {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<InnerApp/>
</AuthProvider>
</QueryClientProvider>

)
}

const rootElement = document.getElementById('app')!

if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(<App/>)
}
const router = createRouter({
routeTree,
defaultPendingComponent: () => (
<div className={`p-2 text-2xl`}>
todo: spinner loading
</div>
),

defaultErrorComponent: ({error}) => <ErrorComponent error={error}/>,
context: {
auth: undefined!, // We'll inject this when we render
},
defaultPreload: 'intent',
})

const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
})


declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}

function InnerApp() {

const auth = useAuth();
return <RouterProvider router={router} context={{auth}}/>
}

function App() {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<InnerApp/>
</AuthProvider>
</QueryClientProvider>

)
}

const rootElement = document.getElementById('app')!

if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(<App/>)
}
11 Replies
wise-white
wise-whiteOP2y ago
and my __root.tsx
interface RouterContext {
auth: AuthContext
}

export const Route = createRootRouteWithContext<RouterContext>()({
component: RootComponent,
})

function RootComponent() {
const context = Route.useRouteContext();

console.log(context)

return (
<>
<div className="p-2 flex gap-2 text-lg">
<Link
to="/"
activeProps={{
className: 'font-bold',
}}
activeOptions={{ exact: true }}
>
Home
</Link>{' '}
{context.auth.user ? (
<Link
to={'/account'}
activeProps={{
className: 'font-bold',
}}
>
Dashboard
</Link>
) : (
<Link
to={'/account'}
activeProps={{
className: 'font-bold',
}}
search={{ redirect: '/' }}
>
Login
</Link>
)}
</div>
<hr />
<Outlet />
<TanStackRouterDevtools position="bottom-right" initialIsOpen={false} />
</>
)
}
interface RouterContext {
auth: AuthContext
}

export const Route = createRootRouteWithContext<RouterContext>()({
component: RootComponent,
})

function RootComponent() {
const context = Route.useRouteContext();

console.log(context)

return (
<>
<div className="p-2 flex gap-2 text-lg">
<Link
to="/"
activeProps={{
className: 'font-bold',
}}
activeOptions={{ exact: true }}
>
Home
</Link>{' '}
{context.auth.user ? (
<Link
to={'/account'}
activeProps={{
className: 'font-bold',
}}
>
Dashboard
</Link>
) : (
<Link
to={'/account'}
activeProps={{
className: 'font-bold',
}}
search={{ redirect: '/' }}
>
Login
</Link>
)}
</div>
<hr />
<Outlet />
<TanStackRouterDevtools position="bottom-right" initialIsOpen={false} />
</>
)
}
so the context in my __root is reset so the user seems non-authentificated on reload (like if I refresh the page) but when i click on a component, the context is updated and user appear as authentifcated this is my AuthProvider
export interface AuthContext {
isAuthenticated: boolean
setUser: (user: User | null) => void
user: User | null
}

const AuthContext = React.createContext<AuthContext | null>(null)

export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = React.useState<User | null>(null)
const isAuthenticated = !!user

const session = useSession();

if (session.data && !user) {
setUser(session.data)
}

return (
<AuthContext.Provider value={{ isAuthenticated, user, setUser }}>
{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
}
export interface AuthContext {
isAuthenticated: boolean
setUser: (user: User | null) => void
user: User | null
}

const AuthContext = React.createContext<AuthContext | null>(null)

export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = React.useState<User | null>(null)
const isAuthenticated = !!user

const session = useSession();

if (session.data && !user) {
setUser(session.data)
}

return (
<AuthContext.Provider value={{ isAuthenticated, user, setUser }}>
{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
}
anyone know how I can handle that ?
wise-white
wise-whiteOP2y ago
No description
wise-white
wise-whiteOP2y ago
the context in tanstack is good but not the context in __root.tsx I did that in my __root.tsx
const context = useRouter().options.context
const context = useRouter().options.context
but there is a small gap that show 'login' then show 'dashboard'
flat-fuchsia
flat-fuchsia2y ago
Your useSession() likely isn't doing any data fetching. Maybe let your AuthContext be a bit more reactive and out the unneeded types.
export function AuthProvider({ children }: { children: React.ReactNode }) {
let user = null

const session = useSession();
const isAuthenticated = !!user

if (session.data && !user) {
user = session.data
}

return (
<AuthContext.Provider value={{ isAuthenticated, user }}>
{children}
</AuthContext.Provider>
)
}
export function AuthProvider({ children }: { children: React.ReactNode }) {
let user = null

const session = useSession();
const isAuthenticated = !!user

if (session.data && !user) {
user = session.data
}

return (
<AuthContext.Provider value={{ isAuthenticated, user }}>
{children}
</AuthContext.Provider>
)
}
It may also just be easier to call the useSession hook in the rootComponent since its just a single place.
wise-white
wise-whiteOP2y ago
my useSession is calling the API to see if the session still OK
export const useSession = (user: User | null) => {


const {data, error, isLoading} = useQuery({
queryKey: ['getUser'],

queryFn: async () => {

if (user) return user;

const {data} = await axios.get(import.meta.env.VITE_API_URL + 'api/v1/users', {
withCredentials: true
})
return data as User;
}
}
)
return {data, error, isLoading}
}
export const useSession = (user: User | null) => {


const {data, error, isLoading} = useQuery({
queryKey: ['getUser'],

queryFn: async () => {

if (user) return user;

const {data} = await axios.get(import.meta.env.VITE_API_URL + 'api/v1/users', {
withCredentials: true
})
return data as User;
}
}
)
return {data, error, isLoading}
}
flat-fuchsia
flat-fuchsia2y ago
Are you injecting the queryClient into the router context? Then,
// useSession file
import { queryOptions } from '@tanstack/react-query';
const sessionQueryOptions = queryOptions({ queryKey: ['getUser'], ... })

const useSession = () => useQuer(sessionQueryOptions);

// router
const route = createRoute({ ..., context: { queryClient } });

// __root.tsx
const Route = createRootRouteWithContext({ ...,
loader: async ({ context }) => {
await context.queryClient.ensureQueryData(sessionQueryOptions)
}
})
// useSession file
import { queryOptions } from '@tanstack/react-query';
const sessionQueryOptions = queryOptions({ queryKey: ['getUser'], ... })

const useSession = () => useQuer(sessionQueryOptions);

// router
const route = createRoute({ ..., context: { queryClient } });

// __root.tsx
const Route = createRootRouteWithContext({ ...,
loader: async ({ context }) => {
await context.queryClient.ensureQueryData(sessionQueryOptions)
}
})
wise-white
wise-whiteOP2y ago
mhh okay, i'll try that what router is related to ? edit: i'm 100% dumb Should I remove the AuthProvider ?? (fetching the user data using useSession)
wise-white
wise-whiteOP2y ago
because doing that in the root include that the user is logged, but root contains also things that are showed if the user is unlogged, creating that
No description
flat-fuchsia
flat-fuchsia2y ago
Its upto you. If you are storing additional data alongside the user then you keep it, else discard it. Authenticated routes and views, heavily relies on your exact setup.
wise-white
wise-whiteOP2y ago
__root is my main page, but I still need the context I just want the context and avoid the 'blinking component' that update itself before and after fetching because using the ensureQueryData, it lock my route if the user is not logged in, and block the access to the website on the root component
flat-fuchsia
flat-fuchsia2y ago
The error can very simply be handled in a try-catch block and performing a no-op on the case of an error being present. You could even check for the kind of error being thrown and handle it based on that. If you are concerned about nothing being shown to the user whilst the ensureQueryData is being resolved, then you could set a defaultPendingComponent on the router instance which'll be shown while the app suspends completing the fetch. Just something to consider, is that the root route is not necessarily a layout route.

Did you find this page helpful?