@daveyplate/better-auth-tanstack

中文文档
1.3.6发布于 2025/05/31MITTypeScriptESM + CJS零依赖近 30 天下载 107.4万

Tanstack hooks for better-auth

原始使用文档
文档目录21 节
  1. Prerequisites
  2. Installation
  3. Setting up the AuthQueryProvider
  4. app/providers.tsx
  5. app/layout.tsx
  6. AuthQueryProvider Props
  7. Creating auth-hooks.ts
  8. Using the Hooks
  9. useSession
  10. useToken
  11. useListAccounts
  12. Usage
  13. useListSessions
  14. useListDeviceSessions
  15. useListPasskeys
  16. Mutations - updateUser
  17. Server-Side Prefetch - Advanced Usage
  18. SSR prefetchSession
  19. useAuthQuery
  20. useAuthMutation
  21. License

@daveyplate/better-auth-tanstack

Tanstack Query hooks for Better Auth. Provides tighter control of when requests are made, optimistic mutations, and supports offline caching for offline auth via Persist Cients.

More to come soon... (e.g. more SSR prefetches, organization & stripe plugin hooks, SWR port...)

☕️ Buy me a coffee

Prerequisites

First, you need to install and integrate Better Auth & Tanstack Query.

Installation

pnpm

pnpm add @daveyplate/better-auth-tanstack@latest

npm

npm install @daveyplate/better-auth-tanstack@latest

For the useSession hook to refresh on sign in, sign out, and sign up without email verification, you must manually call refetch or queryClient.resetQueries() for ["session"] in the onSuccess callback of each of those functions or after awaiting and checking for an error.

If you are using Next.js App Router with protected middleware routes, router.refresh() is required as well to clear the router cache.

@daveyplate/better-auth-ui Better Auth UI supports better-auth-tanstack out of the box! You simply need to use the AuthUIProviderTanstack.

Setting up the AuthQueryProvider

First, you need to set up the AuthQueryProvider in your application. This provider will supply the necessary context for the hooks to function. Requires "use client" directive for Next.js App Router.

app/providers.tsx

"use client"

import { AuthQueryProvider } from "@daveyplate/better-auth-tanstack"

export default function Providers({
  children,
}: {
  children: React.ReactNode
}) {
    return (
        <AuthQueryProvider>
            {children}
        </AuthQueryProvider>
    )
}

app/layout.tsx

import { Providers } from "./providers"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
    return (
        <Providers>
            {children}
        </Providers>
    )
}

AuthQueryProvider Props

The AuthQueryProvider component accepts the following props. The default staleTime for useSession is 60 seconds and for useToken is 10 minutes.

PropTypeDescription
queryOptions?UseQueryOptionsOptional query options for the provider.
sessionQueryOptions?UseQueryOptionsOptional query options for the session query.
tokenQueryOptions?UseQueryOptionsOptional query options for the token query.
sessionKey?string[]Optional key for the session query. The default is ["session"].
tokenKey?string[]Optional key for the token query. The default is ["token"].
listAccountsKey?string[]Optional key for the listAccounts query. The default is ["list-accounts"].
listSessionsKey?string[]Optional key for the listSessions query. The default is ["list-sessions"].
optimistic?booleanWhether to perform optimistic updates. The default is true.
refetchOnMutate?booleanWhether to refetch after mutates. The default is true.

Creating auth-hooks.ts

Create a file named auth-hooks.ts and set up the hooks using createAuthHooks function. This function takes the authClient instance and returns the hooks with full type safety and inference from your authClient.

import { createAuthHooks } from "@daveyplate/better-auth-tanstack"
import { authClient } from "@/lib/auth-client"

export const authHooks = createAuthHooks(authClient)

export const {
    useSession,
    usePrefetchSession,
    useToken,
    useListAccounts,
    useListSessions,
    useListDeviceSessions,
    useListPasskeys,
    useUpdateUser,
    useUnlinkAccount,
    useRevokeOtherSessions,
    useRevokeSession,
    useRevokeSessions,
    useSetActiveSession,
    useRevokeDeviceSession,
    useDeletePasskey,
    useAuthQuery,
    useAuthMutation
} = authHooks

Using the Hooks

useSession

The useSession hook is used to fetch the session.

Props

PropTypeDescription
options?UseQueryOptionsOptional query options for the session query.

Example

import { useSession } from "@/hooks/auth-hooks"

function MyComponent() {
    const { 
        data: sessionData, 
        session, 
        user, 
        isPending, 
        refetch, 
        error
    } = useSession()

    if (isPending) return <div>Loading...</div>

    return <div>Welcome, {user?.email}</div>
}

useToken

The useToken hook is used to fetch the JWT token if better-auth JWT plugin is enabled.

Props

PropTypeDescription
options?UseQueryOptionsOptional query options for the token query.

Example

import { useToken } from "@/hooks/auth-hooks"

function MyComponent() {
    const { data, token, payload, isPending, error } = useToken()

    if (isPending) return <div>Loading...</div>

    return <div>JWT: {token}</div>
}

useListAccounts

The useListAccounts hook allows you to list and manage user accounts linked to different providers.

Usage

import { useListAccounts } from "@/hooks/auth-hooks"

function AccountList() {
  const { data: accounts, isPending, error } = useListAccounts()

  if (isPending) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>

  return (
    <div>
      <h2>Linked Accounts</h2>
      <ul>
        {accounts?.map(account => (
          <li key={account.id}>
            {account.provider}
            <button onClick={() => unlinkAccount(account.provider)}>Unlink</button>
          </li>
        ))}
      </ul>
    </div>
  )
}

Use the unlinkAccount function to unlink an account by provider ID. This is the optimistic example. See below for non-optimistic examples.

unlinkAccount({ providerId: "github" })

useListSessions

import { useListSessions, useRevokeSession } from "@/hooks/auth-hooks"

function SessionList() {
  const { 
    data: sessions, 
    isPending,
    error
   } = useListSessions()
   const { mutate: revokeSession } = useRevokeSession()

  if (isPending) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>

  return (
    <div>
      <h2>Active Sessions</h2>
      <ul>
        {sessions?.map(session => (
          <li key={session.id}>
            {session.userAgent}
            <button onClick={() => revokeSession(session.token)}>Revoke</button>
          </li>
        ))}
      </ul>
    </div>
  )
}

useListDeviceSessions

  const { 
    data: deviceSessions, 
    isPending,
    error
   } = useListDeviceSessions()

useListPasskeys

const {
    data: passkeys,
    isPending,
    error
} = useListPasskeys()

Mutations - updateUser

Optimistic example

Optimistic example to update user's name with no loaders. Optimistically updates the user in the Tanstack Query cache instantly. Revalidates on success, reverts on error. Uses the default setting for optimistic (true) prop on AuthQueryProvider.

This package supports the Tanstack Query global error configuration:

queryClient.getQueryCache().config.onError gets called automatically, so you can set up global error toasts to show error.message || error.statusText. Tanstack Query Global Error Callbacks

"use client"

import { useState } from "react"

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { useSession, useUpdateUser } from "@/hooks/auth-hooks"

export default function SettingsPage() {
    const { user, isPending } = useSession()
    const { mutate: updateUser, error: updateError } = useUpdateUser()
    const [disabled, setDisabled] = useState(true)

    const updateProfile = (formData: FormData) => {
        const name = formData.get("name") as string

        setDisabled(true)
        updateUser({ name: name })
    }

    useEffect(() => {
        if (updateError) {
            // Show an error Toast
        }
    }, [updateError])

    if (isPending || !user) {
        return (
            <div>
                Loading...
            </div>
        )
    }

    return (
        <div className="flex flex-col items-center gap-4 my-auto">
            <Card className="w-full max-w-md">
                <CardHeader>
                    <CardTitle>
                        Change Name
                    </CardTitle>
                </CardHeader>

                <CardContent>
                    <form
                        action={updateProfile}
                        className="flex flex-col gap-4 items-start"
                    >
                        <Label htmlFor="name">
                            Name
                        </Label>

                        <Input
                            defaultValue={user.name}
                            name="name"
                            placeholder="Name"
                            onChange={() => setDisabled(false)}
                        />

                        <Button disabled={disabled}>
                            Save
                        </Button>
                    </form>
                </CardContent>
            </Card>
        </div>
    )
}

Unoptimistic example

Unoptimistic example with useActionState to show loaders for updating a user's name. Set optimistic to false in the AuthQueryProvider props to disable optimistic cache updates. Sends a request to /api/auth/update-user then updates the user in the Tanstack Query cache after the request is successful. Then revalidates the session by refetching.

Note that we use updateUserAsync

"use client"

import { Loader2 } from "lucide-react"
import { useActionState } from "react"

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { useSession, useUpdateUser } from "@/hooks/auth-hooks"
import { cn } from "@/lib/utils"

export default function SettingsPage() {
    const { user, isPending } = useSession()
    const { mutate: updateUser, error: updateError } = useUpdateUser()
    const [disabled, setDisabled] = useState(true)

    type ActionState = Parameters<typeof updateUser>[0]

    const updateProfile = async (_: ActionState, formData: FormData) => {
        const name = formData.get("name") as string

        setDisabled(true)

        const { error } = await updateUserAsync({ name, fetchOptions: { throw: false } })

        if (error) {
            // Show an error Toast or throw an error to an ErrorBoundary

            setDisabled(false)
        }

        return { name } as ActionState
    }

    const [state, action, isSubmitting] = useActionState(updateProfile, {})

    // useEffect(() => {
    //     if (updateError) {
    //          setDisabled(false)
    //         // Show an error Toast
    //     }
    // }, [updateError])

    if (isPending || !user) {
        return (
            <div>
                Loading...
            </div>
        )
    }

    return (
        <div className="flex flex-col items-center gap-4 my-auto">
            <Card className="w-full max-w-md">
                <CardHeader>
                    <CardTitle>
                        Change Name
                    </CardTitle>
                </CardHeader>

                <CardContent>
                    <form
                        action={action}
                        className="flex flex-col gap-4 items-start"
                    >
                        <Label htmlFor="name">
                            Name
                        </Label>

                        <Input
                            defaultValue={state?.name ?? user.name}
                            name="name"
                            placeholder="Name"
                            onChange={() => setDisabled(false)}
                        />

                        <Button disabled={isSubmitting || disabled}>
                            <span className={cn(isSubmitting && "opacity-0")}>
                                Save
                            </span>

                            {isSubmitting && <Loader2 className="animate-spin absolute" />}
                        </Button>
                    </form>
                </CardContent>
            </Card>
        </div>
    )
}

Server-Side Prefetch - Advanced Usage

If you want to use a hybrid prefetching strategy, this is totally supported.

Tanstack Query - Advanced Server Rendering

SSR prefetchSession

The prefetchSession function is used in the server to prefetch session data and store it in the query client.

Props

PropTypeDescription
authAuthThe server auth instance.
queryClientQueryClientThe query client instance.
headersHeadersThe headers object from the server request.
queryKey?string[]Optional key for the session query. Default is ["session"].

RSC Example

import { prefetchSession } from "@daveyplate/better-auth-tanstack/server"
import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query"
import { headers } from "next/headers"

import { betterAuth } from "better-auth"
import { auth } from "@/lib/auth"

export default async function Page() {
    const queryClient = new QueryClient()

    const { data, session, user } = await prefetchSession(
        auth, queryClient, await headers()
    )

    return (
        <HydrationBoundary state={dehydrate(queryClient)}>
            <ClientPage />
        </HydrationBoundary>
    )
}

useAuthQuery

import type { AnyUseQueryOptions } from "@tanstack/react-query"
import { useAuthQuery } from "@/lib/auth-hooks"
import { authClient } from "@/lib/auth-client"

export function useListDeviceSessions(
    options?: Partial<AnyUseQueryOptions>
) {
    return useAuthQuery({
        queryKey: ["device-sessions"],
        queryFn: authClient.multiSession.listDeviceSessions,
        options
    })
}

useAuthMutation

import { AuthQueryContext, type AuthQueryOptions } from "@daveyplate/better-auth-tanstack"
import { authClient } from "@/lib/auth-client"
import { useAuthMutation } from "@/lib/auth-hooks"

export function useUpdateUser(
    options?: Partial<AuthQueryOptions>
) {
    type SessionData = typeof authClient["$Infer"]["Session"]
    const { sessionKey: queryKey } = useContext(AuthQueryContext)

    return useAuthMutation({
        queryKey,
        mutationFn: authClient.updateUser,
        optimisticData: (params, previousSession: SessionData) => ({
            ...previousSession,
            user: { ...previousSession.user, ...params }
        }),
        options
    })
}

License

This project is licensed under the MIT License. See the LICENSE file for details.

版本与发布记录

@daveyplate/better-auth-tanstack 共发布 46 个版本,首次发布于 2025/02/01;过去 12 个月发布了 0 个版本。

版本发布日期距今
1.3.6latest2025/05/311.4 年前
1.3.52025/05/181.5 年前
1.3.42025/04/151.6 年前
1.3.32025/04/081.6 年前
1.3.22025/04/051.6 年前
1.3.12025/04/041.6 年前

关于 @daveyplate/better-auth-tanstack 的常见问题

@daveyplate/better-auth-tanstack 是什么?

Tanstack hooks for better-auth

如何安装 @daveyplate/better-auth-tanstack?

在项目目录运行 npm install @daveyplate/better-auth-tanstack,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18.0.0)、react-dom(>=18.0.0)、better-auth(>=1.2.8)、@tanstack/query-core(>=5.65.0)、@tanstack/react-query(>=5.65.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@daveyplate/better-auth-tanstack 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@daveyplate/better-auth-tanstack 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@daveyplate/better-auth-tanstack 可以商用吗?

@daveyplate/better-auth-tanstack 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@daveyplate/better-auth-tanstack 的下载量有多少?

根据 npm 官方下载统计,@daveyplate/better-auth-tanstack 在 2026/10/12 – 2026/11/10 这 30 天内被下载 1,074,015 次,日均约 35,801 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@daveyplate/better-auth-tanstack 还在维护吗?

最新版本 1.3.6 发布于 2025/05/31(1.4 年前);至今共发布 46 个版本,其中过去 12 个月发布了 0 个;首次发布于 2025/02/01。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。