Zustand 示例

Zustand 状态管理库示例,包括基础存储设置、中间件、持久化和高级模式

💻 基础 Zustand 存储 typescript

🟢 simple ⭐⭐

基础的 Zustand 存储模式,包括创建、更新和在React组件中使用状态

⏱️ 20 min 🏷️ zustand, state-management, react, typescript
Prerequisites: React basics, TypeScript, Hooks
// Zustand Basic Store Examples
// Zustand is a small, fast, and scalable state management solution for React

// 1. Basic Counter Store
import { create } from 'zustand'

interface CounterState {
  count: number
  increment: () => void
  decrement: () => void
  reset: () => void
}

const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}))

// Usage in component
function Counter() {
  const { count, increment, decrement, reset } = useCounterStore()

  return (
    <div className="p-4 border rounded-lg">
      <h2 className="text-xl font-bold mb-4">Counter: {count}</h2>
      <div className="space-x-2">
        <button onClick={increment} className="px-4 py-2 bg-blue-500 text-white rounded">
          Increment
        </button>
        <button onClick={decrement} className="px-4 py-2 bg-red-500 text-white rounded">
          Decrement
        </button>
        <button onClick={reset} className="px-4 py-2 bg-gray-500 text-white rounded">
          Reset
        </button>
      </div>
    </div>
  )
}

// 2. Todo Store with CRUD Operations
interface Todo {
  id: string
  text: string
  completed: boolean
  createdAt: Date
}

interface TodoState {
  todos: Todo[]
  addTodo: (text: string) => void
  toggleTodo: (id: string) => void
  deleteTodo: (id: string) => void
  updateTodo: (id: string, text: string) => void
  clearCompleted: () => void
  completedCount: number
  totalCount: number
}

const useTodoStore = create<TodoState>((set, get) => ({
  todos: [],
  addTodo: (text: string) => {
    const newTodo: Todo = {
      id: Date.now().toString(),
      text,
      completed: false,
      createdAt: new Date(),
    }
    set((state) => ({ todos: [...state.todos, newTodo] }))
  },
  toggleTodo: (id: string) =>
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      ),
    })),
  deleteTodo: (id: string) =>
    set((state) => ({
      todos: state.todos.filter((todo) => todo.id !== id),
    })),
  updateTodo: (id: string, text: string) =>
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, text } : todo
      ),
    })),
  clearCompleted: () =>
    set((state) => ({
      todos: state.todos.filter((todo) => !todo.completed),
    })),
  get completedCount() {
    return get().todos.filter((todo) => todo.completed).length
  },
  get totalCount() {
    return get().todos.length
  },
}))

// Todo Component
function TodoList() {
  const {
    todos,
    addTodo,
    toggleTodo,
    deleteTodo,
    updateTodo,
    clearCompleted,
    completedCount,
    totalCount,
  } = useTodoStore()

  const [newTodoText, setNewTodoText] = useState('')

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (newTodoText.trim()) {
      addTodo(newTodoText.trim())
      setNewTodoText('')
    }
  }

  return (
    <div className="max-w-md mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">Todo List</h1>

      <form onSubmit={handleSubmit} className="mb-4">
        <div className="flex space-x-2">
          <input
            type="text"
            value={newTodoText}
            onChange={(e) => setNewTodoText(e.target.value)}
            placeholder="Add a new todo..."
            className="flex-1 px-3 py-2 border rounded"
          />
          <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded">
            Add
          </button>
        </div>
      </form>

      <div className="mb-4 text-sm text-gray-600">
        {completedCount} of {totalCount} completed
      </div>

      <ul className="space-y-2">
        {todos.map((todo) => (
          <li key={todo.id} className="flex items-center space-x-2">
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
              className="w-4 h-4"
            />
            <input
              type="text"
              value={todo.text}
              onChange={(e) => updateTodo(todo.id, e.target.value)}
              className={`flex-1 px-2 py-1 border rounded ${
                todo.completed ? 'line-through text-gray-500' : ''
              }`}
            />
            <button
              onClick={() => deleteTodo(todo.id)}
              className="px-2 py-1 bg-red-500 text-white rounded text-sm"
            >
              Delete
            </button>
          </li>
        ))}
      </ul>

      {completedCount > 0 && (
        <button
          onClick={clearCompleted}
          className="mt-4 px-4 py-2 bg-gray-500 text-white rounded"
        >
          Clear Completed ({completedCount})
        </button>
      )}
    </div>
  )
}

// 3. Shopping Cart Store
interface CartItem {
  id: string
  name: string
  price: number
  quantity: number
  image?: string
}

interface CartState {
  items: CartItem[]
  addItem: (item: Omit<CartItem, 'quantity'>) => void
  removeItem: (id: string) => void
  updateQuantity: (id: string, quantity: number) => void
  clearCart: () => void
  getTotalPrice: () => number
  getTotalItems: () => number
  isInCart: (id: string) => boolean
}

const useCartStore = create<CartState>((set, get) => ({
  items: [],
  addItem: (item) =>
    set((state) => {
      const existingItem = state.items.find((i) => i.id === item.id)
      if (existingItem) {
        return {
          items: state.items.map((i) =>
            i.id === item.id
              ? { ...i, quantity: i.quantity + 1 }
              : i
          ),
        }
      }
      return { items: [...state.items, { ...item, quantity: 1 }] }
    }),
  removeItem: (id) =>
    set((state) => ({
      items: state.items.filter((item) => item.id !== id),
    })),
  updateQuantity: (id, quantity) =>
    set((state) => ({
      items: state.items.map((item) =>
        item.id === id ? { ...item, quantity: Math.max(1, quantity) } : item
      ),
    })),
  clearCart: () => set({ items: [] }),
  getTotalPrice: () =>
    get().items.reduce((total, item) => total + item.price * item.quantity, 0),
  getTotalItems: () =>
    get().items.reduce((total, item) => total + item.quantity, 0),
  isInCart: (id) => get().items.some((item) => item.id === id),
}))

// Cart Component
function ShoppingCart() {
  const {
    items,
    addItem,
    removeItem,
    updateQuantity,
    clearCart,
    getTotalPrice,
    getTotalItems,
  } = useCartStore()

  const sampleProducts = [
    { id: '1', name: 'Laptop', price: 999, image: '/laptop.jpg' },
    { id: '2', name: 'Mouse', price: 29, image: '/mouse.jpg' },
    { id: '3', name: 'Keyboard', price: 79, image: '/keyboard.jpg' },
  ]

  return (
    <div className="max-w-4xl mx-auto p-4">
      <h1 className="text-2xl font-bold mb-6">Shopping Cart</h1>

      <div className="grid md:grid-cols-2 gap-8">
        {/* Products */}
        <div>
          <h2 className="text-xl font-semibold mb-4">Products</h2>
          <div className="space-y-4">
            {sampleProducts.map((product) => (
              <div key={product.id} className="border p-4 rounded-lg">
                <div className="flex justify-between items-start">
                  <div>
                    <h3 className="font-semibold">{product.name}</h3>
                    <p className="text-gray-600">${product.price}</p>
                  </div>
                  <button
                    onClick={() => addItem(product)}
                    className="px-3 py-1 bg-blue-500 text-white rounded"
                  >
                    Add to Cart
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Cart */}
        <div>
          <h2 className="text-xl font-semibold mb-4">
            Cart ({getTotalItems()} items)
          </h2>

          {items.length === 0 ? (
            <p className="text-gray-500">Your cart is empty</p>
          ) : (
            <>
              <div className="space-y-2 mb-4">
                {items.map((item) => (
                  <div key={item.id} className="border p-3 rounded">
                    <div className="flex justify-between items-center">
                      <div>
                        <h4 className="font-semibold">{item.name}</h4>
                        <p className="text-gray-600">${item.price}</p>
                      </div>
                      <div className="flex items-center space-x-2">
                        <input
                          type="number"
                          min="1"
                          value={item.quantity}
                          onChange={(e) =>
                            updateQuantity(item.id, parseInt(e.target.value))
                          }
                          className="w-16 px-2 py-1 border rounded"
                        />
                        <button
                          onClick={() => removeItem(item.id)}
                          className="px-2 py-1 bg-red-500 text-white rounded text-sm"
                        >
                          Remove
                        </button>
                      </div>
                    </div>
                    <p className="text-sm text-gray-600 mt-1">
                      Subtotal: ${(item.price * item.quantity).toFixed(2)}
                    </p>
                  </div>
                ))}
              </div>

              <div className="border-t pt-4">
                <div className="flex justify-between text-lg font-semibold">
                  <span>Total:</span>
                  <span>${getTotalPrice().toFixed(2)}</span>
                </div>
                <div className="mt-4 space-x-2">
                  <button className="px-4 py-2 bg-green-500 text-white rounded">
                    Checkout
                  </button>
                  <button
                    onClick={clearCart}
                    className="px-4 py-2 bg-gray-500 text-white rounded"
                  >
                    Clear Cart
                  </button>
                </div>
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  )
}

// 4. User Authentication Store
interface User {
  id: string
  name: string
  email: string
  avatar?: string
  role: 'admin' | 'user'
}

interface AuthState {
  user: User | null
  isAuthenticated: boolean
  isLoading: boolean
  login: (email: string, password: string) => Promise<void>
  logout: () => void
  register: (name: string, email: string, password: string) => Promise<void>
  updateProfile: (data: Partial<User>) => Promise<void>
}

const useAuthStore = create<AuthState>((set, get) => ({
  user: null,
  isAuthenticated: false,
  isLoading: false,

  login: async (email: string, password: string) => {
    set({ isLoading: true })
    try {
      // Simulate API call
      await new Promise((resolve) => setTimeout(resolve, 1000))

      // Mock successful login
      const user: User = {
        id: '1',
        name: 'John Doe',
        email,
        role: 'user',
        avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e',
      }

      set({ user, isAuthenticated: true, isLoading: false })
    } catch (error) {
      set({ isLoading: false })
      throw error
    }
  },

  logout: () => {
    set({ user: null, isAuthenticated: false })
  },

  register: async (name: string, email: string, password: string) => {
    set({ isLoading: true })
    try {
      // Simulate API call
      await new Promise((resolve) => setTimeout(resolve, 1000))

      const user: User = {
        id: Date.now().toString(),
        name,
        email,
        role: 'user',
      }

      set({ user, isAuthenticated: true, isLoading: false })
    } catch (error) {
      set({ isLoading: false })
      throw error
    }
  },

  updateProfile: async (data: Partial<User>) => {
    const currentUser = get().user
    if (!currentUser) return

    try {
      // Simulate API call
      await new Promise((resolve) => setTimeout(resolve, 500))

      const updatedUser = { ...currentUser, ...data }
      set({ user: updatedUser })
    } catch (error) {
      throw error
    }
  },
}))

// Authentication Components
function LoginForm() {
  const { login, isLoading } = useAuthStore()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    try {
      await login(email, password)
    } catch (error) {
      alert('Login failed')
    }
  }

  return (
    <form onSubmit={handleSubmit} className="max-w-md mx-auto p-4">
      <h2 className="text-xl font-bold mb-4">Login</h2>
      <div className="space-y-4">
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Email"
          className="w-full px-3 py-2 border rounded"
          required
        />
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="Password"
          className="w-full px-3 py-2 border rounded"
          required
        />
        <button
          type="submit"
          disabled={isLoading}
          className="w-full px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
        >
          {isLoading ? 'Logging in...' : 'Login'}
        </button>
      </div>
    </form>
  )
}

function UserProfile() {
  const { user, logout, updateProfile } = useAuthStore()
  const [isEditing, setIsEditing] = useState(false)
  const [editName, setEditName] = useState(user?.name || '')

  const handleUpdate = async () => {
    try {
      await updateProfile({ name: editName })
      setIsEditing(false)
    } catch (error) {
      alert('Update failed')
    }
  }

  return (
    <div className="max-w-md mx-auto p-4">
      <h2 className="text-xl font-bold mb-4">Profile</h2>

      <div className="space-y-4">
        {user?.avatar && (
          <img
            src={user.avatar}
            alt={user.name}
            className="w-20 h-20 rounded-full"
          />
        )}

        <div>
          {isEditing ? (
            <div className="flex space-x-2">
              <input
                type="text"
                value={editName}
                onChange={(e) => setEditName(e.target.value)}
                className="flex-1 px-3 py-2 border rounded"
              />
              <button
                onClick={handleUpdate}
                className="px-3 py-2 bg-green-500 text-white rounded"
              >
                Save
              </button>
              <button
                onClick={() => setIsEditing(false)}
                className="px-3 py-2 bg-gray-500 text-white rounded"
              >
                Cancel
              </button>
            </div>
          ) : (
            <div>
              <p className="font-semibold">{user?.name}</p>
              <p className="text-gray-600">{user?.email}</p>
              <p className="text-sm text-gray-500">Role: {user?.role}</p>
              <button
                onClick={() => {
                  setEditName(user?.name || '')
                  setIsEditing(true)
                }}
                className="mt-2 px-3 py-1 bg-blue-500 text-white rounded text-sm"
              >
                Edit Profile
              </button>
            </div>
          )}
        </div>

        <button
          onClick={logout}
          className="px-4 py-2 bg-red-500 text-white rounded"
        >
          Logout
        </button>
      </div>
    </div>
  )
}

💻 高级 Zustand 模式 typescript

🟡 intermediate ⭐⭐⭐⭐

复杂的 Zustand 模式,包括中间件、持久化、选择器和性能优化

⏱️ 40 min 🏷️ zustand, advanced, state-management, middleware
Prerequisites: Zustand basics, React hooks, TypeScript, Testing fundamentals
// Advanced Zustand Patterns

// 1. Persistence with Middleware
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

interface UserPreferences {
  theme: 'light' | 'dark'
  language: string
  notifications: boolean
  fontSize: 'small' | 'medium' | 'large'
}

const usePreferencesStore = create<UserPreferences>()(
  persist(
    (set) => ({
      theme: 'light',
      language: 'en',
      notifications: true,
      fontSize: 'medium',
      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language }),
      toggleNotifications: () => set((state) => ({
        notifications: !state.notifications
      })),
      setFontSize: (fontSize) => set({ fontSize }),
    }),
    {
      name: 'user-preferences',
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({
        theme: state.theme,
        language: state.language,
        fontSize: state.fontSize,
      }),
    }
  )
)

// 2. DevTools Middleware
import { devtools } from 'zustand/middleware'

interface AppStore {
  users: User[]
  posts: Post[]
  addUser: (user: User) => void
  addPost: (post: Post) => void
  removeUser: (id: string) => void
  removePost: (id: string) => void
}

const useAppStore = create<AppStore>()(
  devtools(
    (set) => ({
      users: [],
      posts: [],
      addUser: (user) => set((state) => ({
        users: [...state.users, user]
      })),
      addPost: (post) => set((state) => ({
        posts: [...state.posts, post]
      })),
      removeUser: (id) => set((state) => ({
        users: state.users.filter(user => user.id !== id)
      })),
      removePost: (id) => set((state) => ({
        posts: state.posts.filter(post => post.id !== id)
      })),
    }),
    {
      name: 'app-store',
      anonymousActionType: 'unknown',
    }
  )
)

// 3. Subscribe to Store Changes
const useLogger = () => {
  const subscribe = useAppStore.subscribe

  useEffect(() => {
    const unsubscribe = subscribe((state) => {
      console.log('Store updated:', state)
    })

    return unsubscribe
  }, [subscribe])
}

// Select specific state changes
useAppStore.subscribe(
  (state) => state.users,
  (users) => {
    console.log('Users changed:', users)
  }
)

// 4. Combining Multiple Stores
const useCombinedStore = () => {
  const preferences = usePreferencesStore()
  const cart = useCartStore()

  const getLocalizedPrice = (price: number) => {
    const formatter = new Intl.NumberFormat(preferences.language, {
      style: 'currency',
      currency: preferences.language === 'en' ? 'USD' : 'EUR',
    })
    return formatter.format(price)
  }

  return {
    ...preferences,
    ...cart,
    getLocalizedPrice,
  }
}

// 5. Async Actions with Error Handling
interface AsyncStore {
  data: any[]
  loading: boolean
  error: string | null
  fetchData: () => Promise<void>
  createItem: (item: any) => Promise<void>
  updateItem: (id: string, updates: any) => Promise<void>
  deleteItem: (id: string) => Promise<void>
  clearError: () => void
}

const useAsyncStore = create<AsyncStore>((set, get) => ({
  data: [],
  loading: false,
  error: null,

  clearError: () => set({ error: null }),

  fetchData: async () => {
    set({ loading: true, error: null })
    try {
      const response = await fetch('/api/data')
      const data = await response.json()
      set({ data, loading: false })
    } catch (error) {
      set({
        error: error instanceof Error ? error.message : 'Failed to fetch data',
        loading: false
      })
    }
  },

  createItem: async (item) => {
    set({ loading: true, error: null })
    try {
      const response = await fetch('/api/data', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(item),
      })
      const newItem = await response.json()

      set((state) => ({
        data: [...state.data, newItem],
        loading: false
      }))
    } catch (error) {
      set({
        error: error instanceof Error ? error.message : 'Failed to create item',
        loading: false
      })
    }
  },

  updateItem: async (id, updates) => {
    set({ loading: true, error: null })
    try {
      const response = await fetch(`/api/data/${id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updates),
      })
      const updatedItem = await response.json()

      set((state) => ({
        data: state.data.map(item =>
          item.id === id ? { ...item, ...updatedItem } : item
        ),
        loading: false
      }))
    } catch (error) {
      set({
        error: error instanceof Error ? error.message : 'Failed to update item',
        loading: false
      })
    }
  },

  deleteItem: async (id) => {
    set({ loading: true, error: null })
    try {
      await fetch(`/api/data/${id}`, { method: 'DELETE' })

      set((state) => ({
        data: state.data.filter(item => item.id !== id),
        loading: false
      }))
    } catch (error) {
      set({
        error: error instanceof Error ? error.message : 'Failed to delete item',
        loading: false
      })
    }
  },
}))

// 6. Custom Selectors for Performance
const useGameStore = create<GameStore>()(
  devtools(
    persist(
      (set, get) => ({
        players: [],
        enemies: [],
        items: [],
        score: 0,
        level: 1,
        health: 100,

        // Actions
        addPlayer: (player) => set((state) => ({
          players: [...state.players, player]
        })),

        spawnEnemy: (enemy) => set((state) => ({
          enemies: [...state.enemies, enemy]
        })),

        collectItem: (item) => set((state) => ({
          items: [...state.items, item],
          score: state.score + item.points
        })),

        takeDamage: (damage) => set((state) => ({
          health: Math.max(0, state.health - damage)
        })),

        levelUp: () => set((state) => ({
          level: state.level + 1,
          health: state.health + 20
        })),
      }),
      {
        name: 'game-store',
        storage: createJSONStorage(() => localStorage),
      }
    )
  )
)

// Custom selectors with memoization
const useGameSelectors = () => {
  const store = useGameStore()

  const aliveEnemies = useStore(useCallback(
    (state) => state.enemies.filter(enemy => enemy.health > 0),
    []
  ))

  const playerHealth = useStore(useCallback(
    (state) => state.health,
    []
  ))

  const isGameOver = useStore(useCallback(
    (state) => state.health <= 0,
    []
  ))

  const highScore = useStore(useCallback(
    (state) => state.score,
    []
  ))

  return {
    aliveEnemies,
    playerHealth,
    isGameOver,
    highScore,
    ...store
  }
}

// 7. Optimistic Updates
interface OptimisticStore {
  posts: Post[]
  updatePostOptimistic: (id: string, updates: Partial<Post>) => Promise<void>
  deletePostOptimistic: (id: string) => Promise<void>
}

const useOptimisticStore = create<OptimisticStore>((set, get) => ({
  posts: [],

  updatePostOptimistic: async (id, updates) => {
    const previousPosts = get().posts

    // Optimistic update
    set((state) => ({
      posts: state.posts.map(post =>
        post.id === id ? { ...post, ...updates } : post
      )
    }))

    try {
      const response = await fetch(`/api/posts/${id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updates),
      })

      if (!response.ok) {
        throw new Error('Update failed')
      }

      const updatedPost = await response.json()

      // Confirm update
      set((state) => ({
        posts: state.posts.map(post =>
          post.id === id ? updatedPost : post
        )
      }))
    } catch (error) {
      // Revert on error
      set({ posts: previousPosts })
      throw error
    }
  },

  deletePostOptimistic: async (id) => {
    const previousPosts = get().posts

    // Optimistic deletion
    set((state) => ({
      posts: state.posts.filter(post => post.id !== id)
    }))

    try {
      const response = await fetch(`/api/posts/${id}`, {
        method: 'DELETE',
      })

      if (!response.ok) {
        throw new Error('Delete failed')
      }
    } catch (error) {
      // Revert on error
      set({ posts: previousPosts })
      throw error
    }
  },
}))

// 8. Testing Zustand Stores
import { act, renderHook } from '@testing-library/react'
import { create } from 'zustand'

// Test store
interface TestStore {
  count: number
  increment: () => void
  decrement: () => void
}

const useTestStore = create<TestStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}))

// Test cases
describe('useTestStore', () => {
  it('should initialize with count 0', () => {
    const { result } = renderHook(() => useTestStore())
    expect(result.current.count).toBe(0)
  })

  it('should increment count', () => {
    const { result } = renderHook(() => useTestStore())

    act(() => {
      result.current.increment()
    })

    expect(result.current.count).toBe(1)
  })

  it('should decrement count', () => {
    const { result } = renderHook(() => useTestStore())

    act(() => {
      result.current.decrement()
    })

    expect(result.current.count).toBe(-1)
  })
})

// 9. TypeScript Best Practices
interface StrictStore {
  readonly state: {
    user: User | null
    status: 'idle' | 'loading' | 'success' | 'error'
  }
  actions: {
    setUser: (user: User) => void
    clearUser: () => void
    setStatus: (status: StrictStore['state']['status']) => void
  }
}

const useStrictStore = create<StrictStore>((set) => ({
  state: {
    user: null,
    status: 'idle' as const,
  },
  actions: {
    setUser: (user) =>
      set((state) => ({
        state: { ...state.state, user }
      })),
    clearUser: () =>
      set((state) => ({
        state: { ...state.state, user: null }
      })),
    setStatus: (status) =>
      set((state) => ({
        state: { ...state.state, status }
      })),
  },
}))

// Usage with destructuring
const { state: { user, status }, actions } = useStrictStore()

// 10. Server-Side Rendering Support
import { create } from 'zustand'
import { createContext, useContext, useRef } from 'react'

// Store interface
interface Store {
  count: number
  increment: () => void
}

// Create store
const createStore = () => create<Store>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))

// Context for store
const StoreContext = createContext<ReturnType<typeof createStore> | null>(null)

// Provider component
export const StoreProvider = ({ children }: { children: React.ReactNode }) => {
  const storeRef = useRef<ReturnType<typeof createStore>>()

  if (!storeRef.current) {
    storeRef.current = createStore()
  }

  return (
    <StoreContext.Provider value={storeRef.current}>
      {children}
    </StoreContext.Provider>
  )
}

// Hook to use store
export const useStore = <T>(selector: (store: Store) => T): T => {
  const store = useContext(StoreContext)

  if (!store) {
    throw new Error('useStore must be used within StoreProvider')
  }

  return useStore(store, selector)
}