# How to Implement Transient Updates Without Re-Renders in Zustand for High-Frequency State Changes

> Implement transient updates in Zustand without re-renders. Learn to use subscribe and useRef directly to handle high-frequency state changes efficiently and boost performance.

- Repository: [Poimandres/zustand](https://github.com/pmndrs/zustand)
- Tags: performance
- Published: 2026-03-06

---

**Use the store's `subscribe` method directly inside a `useEffect` hook to listen for changes, store the latest value in a `useRef` instead of component state, and bypass the `useStore` hook entirely to prevent React re-renders during high-frequency updates.**

Zustand's default React integration triggers a component re-render whenever the store state changes, which creates severe performance bottlenecks for rapid updates like animations or real-time sensor data. By implementing **transient updates without re-renders in Zustand**, you can subscribe to state changes outside of React's rendering cycle using the vanilla store API. This pattern allows components to receive high-frequency updates silently through mutable references rather than state, keeping the UI smooth even at 60Hz or higher.

## Understanding the Re-Render Problem

Zustand's core store (`createStore`) always notifies **all** listeners whenever `setState` changes the internal state. The React hook `useStore`, implemented with `React.useSyncExternalStore` in [[`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts)](https://github.com/pmndrs/zustand/blob/main/src/react.ts) (lines 30-38), subscribes to these notifications to trigger component updates.

While this ensures UI consistency for most use cases, it becomes problematic for high-frequency scenarios like cursor tracking or game loops. Every call to `setState` iterates through the listener `Set` defined in [[`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts)](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) (lines 88-92), causing React to schedule a re-render each time.

## Implementing Transient Updates with subscribe

The transient update pattern bypasses `useStore` entirely by calling the store's `subscribe` method directly within a `useEffect`. Because this subscription exists outside React's state management, the component never re-renders when the listener fires.

Store the latest value in a mutable container (`useRef` or a plain variable) rather than in component state. The subscription mechanism is defined in [`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts), where `setState` computes `nextState` and, if changed, iterates `listeners.forEach(listener => listener(state, previousState))`.

```tsx
import { create } from 'zustand'
import { useEffect, useRef } from 'react'

// Create a vanilla store (no React hook)
const useScratchStore = create((set) => ({
  scratches: 0,
  tick: () => set((s) => ({ scratches: s.scratches + 1 })),
}))

export const ScratchComponent = () => {
  // Hold the latest value in a mutable ref
  const scratchesRef = useRef(useScratchStore.getState().scratches)

  // Subscribe once on mount; update the ref on every change
  useEffect(() => {
    const unsub = useScratchStore.subscribe((state) => {
      scratchesRef.current = state.scratches          // No re-render
    })
    return unsub                                    // Auto-cleanup on unmount
  }, [])

  // Render something that does not depend on scratches
  return <div>Hover over me – values update silently.</div>
}

```

Because `ScratchComponent` never reads `scratchesRef.current` during the render phase, React never schedules an update, allowing the subscription to handle thousands of changes per second without performance degradation.

## Optimizing with Selective Subscriptions

For complex stores where you only need a specific slice of state, the `subscribeWithSelector` middleware reduces unnecessary work. Implemented in [[`src/middleware/subscribeWithSelector.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/subscribeWithSelector.ts)](https://github.com/pmndrs/zustand/blob/main/src/middleware/subscribeWithSelector.ts) (lines 50-68), this middleware allows you to attach a selector to the subscription while still avoiding React re-renders.

The listener only runs when the selected slice actually changes, using `Object.is` for equality checking by default.

```tsx
import { create } from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'
import { useEffect, useRef } from 'react'

const useMouseStore = create(
  subscribeWithSelector(() => ({
    x: 0,
    y: 0,
    move: (dx: number, dy: number) =>
      set((s) => ({ x: s.x + dx, y: s.y + dy })),
  }))
)

export const MouseTracker = () => {
  const positionRef = useRef({ x: 0, y: 0 })

  useEffect(() => {
    const unsub = useMouseStore.subscribe(
      (state) => ({ x: state.x, y: state.y }),   // Selector for specific slice
      (pos) => {
        positionRef.current = pos                // Transient update
      },
      { fireImmediately: true }                  // Initialize with current value
    )
    return unsub
  }, [])

  return <canvas /* draw using positionRef.current */ />
}

```

This approach minimizes memory allocations and comparison overhead when tracking specific properties in high-frequency update streams.

## Practical Use Cases: Animation Loops

Transient updates excel in animation contexts where you need the latest state inside a `requestAnimationFrame` loop without triggering React's render pipeline. The mutable reference provides immediate access to current values while the component renders only once.

```tsx
import { useEffect, useRef } from 'react'
import { useMouseStore } from './mouseStore'

export const AnimatedCanvas = () => {
  const posRef = useRef({ x: 0, y: 0 })

  // Sync posRef with the store (transient subscription)
  useEffect(() => {
    const unsub = useMouseStore.subscribe(
      (s) => ({ x: s.x, y: s.y }),
      (p) => (posRef.current = p),
      { fireImmediately: true }
    )
    return unsub
  }, [])

  // Animation loop reads the mutable ref without causing renders
  useEffect(() => {
    let raf = 0
    const draw = () => {
      const { x, y } = posRef.current
      // Canvas drawing logic here
      raf = requestAnimationFrame(draw)
    }
    draw()
    return () => cancelAnimationFrame(raf)
  }, [])

  return <canvas width={800} height={600} />
}

```

This pattern keeps the animation smooth because the canvas renders independently of React's reconciliation cycle, even when the underlying store updates hundreds of times per second.

## Summary

- **Transient updates** bypass `useStore` to avoid React re-renders by using the vanilla store's `subscribe` method directly.
- Store high-frequency values in `useRef` or plain variables rather than React state to prevent render scheduling.
- The core subscription logic resides in [[`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts)](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts), while the React re-render trigger is located in [[`src/react.ts`](https://github.com/pmndrs/zustand/blob/main/src/react.ts)](https://github.com/pmndrs/zustand/blob/main/src/react.ts) via `useSyncExternalStore`.
- Use the `subscribeWithSelector` middleware from [[`src/middleware/subscribeWithSelector.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/subscribeWithSelector.ts)](https://github.com/pmndrs/zustand/blob/main/src/middleware/subscribeWithSelector.ts) to filter updates and reduce processing overhead.
- Always return the unsubscribe function from `useEffect` to prevent memory leaks when components unmount.

## Frequently Asked Questions

### What is the difference between useStore and store.subscribe in Zustand?

`useStore` is a React hook that uses `useSyncExternalStore` to bind the Zustand store to React's component lifecycle, triggering re-renders whenever subscribed state changes. In contrast, `store.subscribe` is a vanilla JavaScript method from the core store implementation that registers a callback function to run on state changes without any React integration, making it ideal for transient updates that should not cause re-renders.

### Can I use transient updates with the standard create function from Zustand?

Yes. The `create` function returns a hook that also exposes the vanilla store methods including `subscribe`, `getState`, and `setState`. You can access these directly via `useStore.subscribe` or by exporting the store instance separately to subscribe outside of React components while still using the same store definition.

### When should I use subscribeWithSelector instead of basic subscribe?

Use `subscribeWithSelector` when you need to listen to specific slices of a complex state object rather than the entire state. According to the implementation in [`src/middleware/subscribeWithSelector.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/subscribeWithSelector.ts), this middleware adds selector support and equality checking (defaulting to `Object.is`), ensuring your listener only fires when the selected value actually changes, reducing unnecessary computations during high-frequency updates.

### Will transient updates cause memory leaks if I don't unsubscribe?

Yes. The store maintains a `Set` of listeners as implemented in [`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts). If you fail to call the unsubscribe function returned by `store.subscribe` when the component unmounts, the listener remains in the store's internal registry, preventing garbage collection and potentially causing stale closures. Always return the unsubscribe function from the `useEffect` cleanup handler.