You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Zustand中用数据库数据作初始状态?报错排查与最佳实践

问题根源分析

你遇到的所有错误,核心原因是在Zustand的create回调中直接调用React钩子(useSession、trpc.images.list.useQuery)违反了React钩子规则:

  • React钩子必须在组件或自定义钩子的顶层调用,而Zustand的create函数是在组件外部执行的,不属于React渲染上下文,直接调用钩子会触发规则错误,进而引发useContext相关报错(因为useSession和tRPC useQuery内部依赖React Context)。
  • 这种钩子误用还会触发上下文冲突,表现出"多个React副本"或"版本不匹配"的错误提示,但根源是钩子调用位置违规。
修正方案与代码示例

方案1:组件内获取数据后同步到Zustand

将数据获取逻辑移到组件中,拿到数据后调用Zustand的方法更新状态,这是最符合React和Zustand最佳实践的方式。

第一步:调整Zustand Store

type Image = {
  url: string;
  prompt: string;
};

interface UserState {
  images: Image[];
  isLoading: boolean;
  addImage: (url: string, prompt: string) => void;
  removeImage: (url: string) => void;
  setImages: (images: Image[]) => void;
  setLoading: () => void;
}

export const useStore = create<UserState>((set) => ({
  // 初始状态设为空数组,类型简化为Image[](已包含空数组场景)
  images: [],
  isLoading: false,
  addImage: (url, prompt) => set(state => ({
    images: [...state.images, { url, prompt }]
  })),
  removeImage: (url) => set(state => ({
    images: state.images.filter(x => x.url !== url)
  })),
  // 添加同步数据到store的方法
  setImages: (images) => set({ images }),
  setLoading: () => set(state => ({ isLoading: !state.isLoading }))
}));

第二步:在组件中获取数据并同步

import { useStore } from '@/stores/useStore';
import { useSession } from 'next-auth/react';
import { trpc } from '@/trpc/client';

export function ImageGallery() {
  const { data: sessionData } = useSession();
  const { setImages, images } = useStore();
  
  // 在组件内使用tRPC钩子获取数据
  const { isLoading } = trpc.images.list.useQuery({
    limit: 20,
    userId: sessionData?.user?.id ?? "",
  }, {
    // 查询成功后同步到Zustand
    onSuccess: (data) => {
      setImages(data.items || []);
    }
  });

  return (
    <div>
      {isLoading ? <p>加载中...</p> : (
        <div className="image-grid">
          {images.map(img => (
            <div key={img.url}>
              <img src={img.url} alt={img.prompt} />
              <p>{img.prompt}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

方案2:在Store中使用异步方法加载数据

如果需要在Store层面封装数据加载逻辑,可使用tRPC的query方法(非钩子版本),避免触发钩子规则:

调整后的Store

export const useStore = create<UserState>((set) => ({
  images: [],
  isLoading: false,
  // ...其他方法
  // 封装异步加载方法,使用tRPC的query而非useQuery
  loadImages: async (userId: string) => {
    set({ isLoading: true });
    try {
      const data = await trpc.images.list.query({ limit: 20, userId });
      set({ images: data.items || [], isLoading: false });
    } catch (err) {
      console.error('加载图片失败:', err);
      set({ isLoading: false });
    }
  }
}));

在组件中调用加载方法

import { useEffect } from 'react';
import { useStore } from '@/stores/useStore';
import { useSession } from 'next-auth/react';

export function ImageGallery() {
  const { data: sessionData } = useSession();
  const { loadImages, images, isLoading } = useStore();

  // 组件挂载时触发数据加载
  useEffect(() => {
    if (sessionData?.user?.id) {
      loadImages(sessionData.user.id);
    }
  }, [sessionData?.user?.id, loadImages]);

  // ...渲染逻辑
}
最佳实践总结
  • Zustand专注状态管理:数据获取(API调用、tRPC查询)应放在组件或自定义钩子中,避免在Store初始化逻辑中调用React钩子。
  • 严格遵守钩子规则:React钩子只能在组件或自定义钩子的顶层调用,禁止在普通函数(包括Zustand的create回调)中使用。
  • 合理设置初始状态:避免冗余类型(比如Image[] | [],Image[]已经包含空数组场景),直接用[]作为初始值更清晰。
  • 分离数据获取与状态更新:让组件负责数据获取,Store负责状态存储与修改,职责划分更清晰,也更容易维护。

内容的提问来源于stack exchange,提问作者G.Rose

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 16:45:25