---
url: /docs/frontend/state.md
---
# 状态与数据查询

## 状态放在哪里

服务端数据由 TanStack Query 管理；网址可分享的状态放路由查询参数；组件短期状态用 React；跨组件的纯界面状态可使用 Jotai。不要把会话、订阅和积分复制到多个状态容器。

统一查询客户端在 `apps/app/lib/query.ts`：一般查询新鲜期为两分钟，网络重新连接时刷新；变更默认不重试，避免响应丢失后重复创建记录。

## 会话查询

`apps/app/lib/queries/session.ts` 使用固定查询键 `['auth', 'session']`，新鲜期为 30 秒。用户和会话同时存在才算登录；401、403 不自动重试，网络错误可以重试。组件用 `useSessionQuery()`，路由守卫使用同一查询配置。

注册、登录或切换活动组织后调用 `revalidateSession()`，删除旧会话查询并重新运行路由守卫。退出只有在服务端成功后才清空会话并跳转登录。

## 业务查询

查询集中在 `apps/app/lib/queries/`。组织查询键包含组织 ID；没有活动组织时跳过请求。写入成功后只刷新受影响的数据，避免把所有查询一起失效。

```tsx
import { useSessionQuery } from "#lib/queries/session";
import { useMembersQuery } from "#lib/queries/organization";

export function MemberCount() {
  const session = useSessionQuery();
  const members = useMembersQuery(session.data?.session.activeOrganizationId);
  if (members.isPending) return <p>正在读取成员</p>;
  if (members.error) return <p>成员读取失败</p>;
  return <p>成员数量：{members.data?.members.length ?? 0}</p>;
}
```

这个组件示例用于说明查询依赖；无活动组织时应提供创建组织入口，而不是永久显示加载中。

## 排查缓存问题

先检查查询键是否包含用户或组织范围，再检查失效逻辑。权限和账单还需要服务端使用 `ctx.db` 的实时连接，前端刷新无法修正后端缓存了旧权限的问题。
