# How to Use Zustand with Third-Party Libraries Like React Query

> Combine Zustand's reactive state management with React Query's data fetching. Learn how to sync server and local state for a powerful app architecture.

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

---

**You can use Zustand to manage local UI state while letting React Query handle server state, syncing the two via callbacks like `onSuccess` to combine Zustand's lightweight reactivity with robust data fetching.**

Zustand's minimal API makes it ideal for composing with other libraries in the React ecosystem. Because the `pmndrs/zustand` repository exposes only a simple `create` function and an unobtrusive `useStore` hook, you can safely integrate it alongside React Query without architectural conflicts.

## Why Zustand Integrates Seamlessly with Third-Party Libraries

Zustand is intentionally framework-agnostic. According to the repository's [`README.md`](https://github.com/pmndrs/zustand/blob/main/README.md), the core philosophy emphasizes that a store is just a plain JavaScript object with a subscription mechanism. This design means Zustand does not prescribe how data enters the store, allowing you to populate it from any source—including React Query's cache.

The `create` function documented in [`docs/reference/apis/create.md`](https://github.com/pmndrs/zustand/blob/main/docs/reference/apis/create.md) returns a hook that reads from the store, while the underlying store object exposes `setState` methods that any callback can invoke. This separation of concerns lets React Query manage the network layer while Zustand provides the reactive UI layer.

## Separating Server State and Client State

When you use Zustand with React Query, assign distinct responsibilities to each library to avoid duplication and stale data.

| Concern | Tool | Implementation Pattern |
|---------|------|------------------------|
| **Server state** (caching, refetching, background updates) | React Query | Use `useQuery` to fetch data and `onSuccess` to push results into Zustand. |
| **Local UI state** (modals, forms, filters) | Zustand | Store flags and temporary values in a `create` store accessed via `useStore`. |
| **Optimistic updates** | Both | Update Zustand immediately for UI responsiveness, then sync React Query's cache via `setQueryData` or `invalidateQueries`. |
| **Persistence** | Zustand middleware | Use `persist` from [`docs/reference/middlewares/persist.md`](https://github.com/pmndrs/zustand/blob/main/docs/reference/middlewares/persist.md) to save UI state across reloads while React Query refreshes server data. |

## Practical Implementation: Zustand with React Query

### Setting Up the Zustand Store

First, create a store that will hold the data fetched by React Query. The `useStore` hook documented in [`docs/reference/hooks/use-store.md`](https://github.com/pmndrs/zustand/blob/main/docs/reference/hooks/use-store.md) allows components to subscribe to specific slices.

```typescript
// src/store/useTodoStore.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

type Todo = { id: number; title: string; completed: boolean };

interface TodoStore {
  todos: Todo[];
  setTodos: (todos: Todo[]) => void;
  addTodo: (todo: Todo) => void;
  toggle: (id: number) => void;
}

export const useTodoStore = create<TodoStore>()(
  devtools((set) => ({
    todos: [],
    setTodos: (todos) => set({ todos }),
    addTodo: (todo) =>
      set((state) => ({ todos: [...state.todos, todo] })),
    toggle: (id) =>
      set((state) => ({
        todos: state.todos.map((t) =>
          t.id === id ? { ...t, completed: !t.completed } : t
        ),
      })),
  }))
);

```

### Syncing Query Data to Zustand

Use React Query's `onSuccess` callback to write fetched data into the Zustand store. This pattern keeps your components decoupled from the fetching logic while providing instant access to data via Zustand's stable API