Questions d'entretien Zustand 2026 : Gestion d'état React et bonnes pratiques

Préparez vos entretiens techniques avec ce guide complet sur Zustand. Découvrez les questions fréquentes, les patterns middleware, la comparaison avec Context API et les meilleures pratiques TypeScript.

Questions d'entretien Zustand 2026

Zustand s'est imposé comme la bibliothèque de gestion d'état de référence pour les applications React qui nécessitent plus que l'API Context mais moins de complexité que Redux. Avec son API minimaliste et sa conception TypeScript-first, les questions sur Zustand apparaissent désormais régulièrement dans les entretiens frontend, particulièrement pour les postes React de niveau intermédiaire et senior.

Ce que recherchent les recruteurs

Les questions d'entretien sur Zustand évaluent trois domaines : la compréhension du pattern store par rapport à l'état natif React, la connaissance des cas où Zustand surpasse les alternatives, et la capacité à structurer les stores pour la maintenabilité.

Concepts fondamentaux de Zustand à maîtriser

Zustand v5, sorti fin 2024, a introduit plusieurs changements majeurs par rapport à la v4. La fonction create ne nécessite plus d'appel useStore séparé, et le middleware persist utilise désormais un modèle d'hydratation synchrone par défaut. Les recruteurs s'attendent à ce que les candidats connaissent l'API actuelle.

Qu'est-ce qui différencie Zustand de useState et useReducer ?

Les stores Zustand existent en dehors de l'arbre de composants React. Cette différence architecturale a trois conséquences pratiques :

  1. L'état persiste entre les montages et démontages de composants sans providers Context
  2. Les mises à jour ne déclenchent pas de re-rendus parents, seuls les composants abonnés se re-rendent
  3. L'état est accessible de manière synchrone en dehors de React, utile pour les gestionnaires d'événements et la logique asynchrone
store.tstypescript
import { create } from 'zustand'

interface CartStore {
  items: CartItem[]
  addItem: (item: CartItem) => void
  clearCart: () => void
  totalPrice: () => number
}

export const useCartStore = create<CartStore>((set, get) => ({
  items: [],
  addItem: (item) => set((state) => ({ 
    items: [...state.items, item] 
  })),
  clearCart: () => set({ items: [] }),
  totalPrice: () => get().items.reduce((sum, item) => sum + item.price, 0)
}))

La fonction get fournit un accès synchrone à l'état actuel, évitant le problème de closure obsolète courant avec useState.

Comment Zustand gère-t-il les re-rendus ?

Zustand utilise l'égalité superficielle par défaut. Lorsqu'un composant s'abonne à un store avec useCartStore(), il reçoit l'objet d'état complet et se re-rend à chaque changement. Sélectionner des tranches spécifiques empêche les rendus inutiles :

Component.tsxtypescript
// Mauvais : re-rendu à chaque changement du store
const { items } = useCartStore()

// Bon : re-rendu uniquement quand items change
const items = useCartStore((state) => state.items)

// Valeurs multiples : utiliser la comparaison superficielle
import { shallow } from 'zustand/shallow'

const { items, totalPrice } = useCartStore(
  (state) => ({ items: state.items, totalPrice: state.totalPrice() }),
  shallow
)

Le comparateur shallow de zustand/shallow effectue une égalité de référence sur les propriétés de l'objet plutôt que sur l'objet lui-même.

Zustand vs Context API : quand choisir l'un ou l'autre

Cette question de comparaison apparaît dans presque tous les entretiens couvrant la gestion d'état. La réponse dépend de la fréquence des mises à jour et de la complexité de l'état.

CritèreContext APIZustand
Taille du bundle0 Ko (intégré)1,2 Ko gzippé
Contrôle des re-rendusManuel avec memo/useMemoSélecteurs intégrés
DevToolsReact DevTools uniquementCompatible Redux DevTools
Composants serveurSupport completNécessite une frontière client
État asynchroneNécessite un wrapperSupport natif

L'API Context fonctionne bien pour les valeurs qui changent rarement comme le thème ou la locale. Zustand excelle lorsque les mises à jour d'état sont fréquentes, comme les champs de formulaire, les données temps réel ou les paniers d'achat. La documentation React sur la gestion d'état recommande de remonter l'état et d'utiliser Context pour les problèmes de "prop drilling", tandis que les stores externes conviennent aux patterns de mise à jour complexes.

Considération sur les Server Components

Les stores Zustand nécessitent la directive "use client". Pour les applications utilisant intensivement les React Server Components, cela signifie que l'état Zustand vit uniquement dans les composants clients. Les architectures hybrides passent souvent les données récupérées côté serveur comme props aux composants clients qui se synchronisent ensuite avec Zustand.

Patterns middleware dans Zustand

Les questions sur les middleware testent si un candidat comprend la composition plutôt que la configuration. Les middleware Zustand enveloppent la fonction créatrice de store, ajoutant des comportements sans modifier l'API principale.

Comment fonctionne le middleware persist ?

store.tstypescript
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

interface UserPreferences {
  theme: 'light' | 'dark'
  language: string
  setTheme: (theme: 'light' | 'dark') => void
}

export const usePreferencesStore = create<UserPreferences>()(
  persist(
    (set) => ({
      theme: 'light',
      language: 'en',
      setTheme: (theme) => set({ theme })
    }),
    {
      name: 'user-preferences',
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({ theme: state.theme, language: state.language })
    }
  )
)

L'option partialize exclut les fonctions et l'état dérivé de la persistance. Sans elle, la sérialisation échoue ou le stockage gonfle avec des données inutiles. La documentation Zustand sur persist couvre les stratégies de migration pour les changements de schéma.

Combiner plusieurs middleware

Les middleware se composent de l'intérieur vers l'extérieur. Le middleware le plus interne s'exécute en premier :

store.tstypescript
import { create } from 'zustand'
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'

export const useStore = create<StoreState>()(
  devtools(
    persist(
      subscribeWithSelector(
        immer((set) => ({
          // state and actions
        }))
      ),
      { name: 'app-storage' }
    ),
    { name: 'AppStore' }
  )
)

Le middleware immer permet des mises à jour de style mutable qui produisent un état immuable. Le middleware subscribeWithSelector permet de s'abonner à des tranches d'état en dehors des composants React.

Prêt à réussir tes entretiens React / Next.js ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Patterns d'intégration TypeScript

Zustand v5 a considérablement amélioré l'inférence TypeScript. Le pattern de double appel de fonction create<Type>()() fournit une inférence de type complète pour les chaînes de middleware.

Typer les actions qui référencent un autre état

store.tstypescript
interface TodoStore {
  todos: Todo[]
  filter: 'all' | 'active' | 'completed'
  addTodo: (text: string) => void
  toggleTodo: (id: string) => void
  filteredTodos: () => Todo[]
}

export const useTodoStore = create<TodoStore>()((set, get) => ({
  todos: [],
  filter: 'all',
  
  addTodo: (text) => set((state) => ({
    todos: [...state.todos, { id: crypto.randomUUID(), text, completed: false }]
  })),
  
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map((todo) =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )
  })),
  
  filteredTodos: () => {
    const { todos, filter } = get()
    switch (filter) {
      case 'active': return todos.filter((t) => !t.completed)
      case 'completed': return todos.filter((t) => t.completed)
      default: return todos
    }
  }
}))

L'état dérivé en tant que fonctions plutôt que getters évite les valeurs obsolètes. Chaque appel à filteredTodos() lit l'état actuel.

Diviser les stores pour les grandes applications

Une question d'entretien courante demande comment structurer Zustand pour la montée en charge. Le pattern slice divise un store en modules par domaine :

slices/authSlice.tstypescript
import { StateCreator } from 'zustand'

export interface AuthSlice {
  user: User | null
  isAuthenticated: boolean
  login: (credentials: Credentials) => Promise<void>
  logout: () => void
}

export const createAuthSlice: StateCreator<
  AuthSlice & CartSlice,  // Type du store combiné
  [],
  [],
  AuthSlice
> = (set) => ({
  user: null,
  isAuthenticated: false,
  login: async (credentials) => {
    const user = await authApi.login(credentials)
    set({ user, isAuthenticated: true })
  },
  logout: () => set({ user: null, isAuthenticated: false })
})
store.tstypescript
import { create } from 'zustand'
import { createAuthSlice, AuthSlice } from './slices/authSlice'
import { createCartSlice, CartSlice } from './slices/cartSlice'

export const useStore = create<AuthSlice & CartSlice>()((...args) => ({
  ...createAuthSlice(...args),
  ...createCartSlice(...args)
}))

Les slices peuvent se référencer mutuellement grâce au type de store combiné passé à StateCreator.

Questions d'entretien courantes avec réponses modèles

Quand Zustand serait-il un mauvais choix ?

Zustand ajoute de la complexité sans bénéfice dans ces scénarios :

  • Données de configuration statiques qui ne changent jamais à l'exécution
  • État de formulaire géré par React Hook Form ou bibliothèques similaires
  • État serveur mieux géré par TanStack Query ou SWR
  • Passage de props simple parent-enfant avec 2-3 niveaux d'imbrication

La section comparaison du dépôt GitHub Zustand fournit la perspective des mainteneurs sur les alternatives.

Comment tester des composants utilisant Zustand ?

Les tests nécessitent de réinitialiser l'état du store entre les tests et optionnellement de mocker complètement le store :

store.test.tstypescript
import { act, renderHook } from '@testing-library/react'
import { useCartStore } from './store'

beforeEach(() => {
  // Réinitialiser le store à l'état initial
  useCartStore.setState({ items: [], totalItems: 0 })
})

test('addItem increases cart count', () => {
  const { result } = renderHook(() => useCartStore())
  
  act(() => {
    result.current.addItem({ id: '1', name: 'Product', price: 10 })
  })
  
  expect(result.current.items).toHaveLength(1)
})

Pour les tests de composants qui ne doivent pas interagir avec les vrais stores, mocker au niveau du module :

Component.test.tsxtypescript
import { vi } from 'vitest'

vi.mock('./store', () => ({
  useCartStore: vi.fn(() => ({
    items: [{ id: '1', name: 'Mock Product', price: 25 }],
    addItem: vi.fn()
  }))
}))

Comment Zustand gère-t-il les opérations asynchrones ?

Contrairement à Redux, Zustand n'a pas d'exigence de middleware pour l'async. Les actions peuvent être directement des fonctions asynchrones :

store.tstypescript
interface ProductStore {
  products: Product[]
  isLoading: boolean
  error: string | null
  fetchProducts: () => Promise<void>
}

export const useProductStore = create<ProductStore>((set) => ({
  products: [],
  isLoading: false,
  error: null,
  
  fetchProducts: async () => {
    set({ isLoading: true, error: null })
    try {
      const products = await productApi.getAll()
      set({ products, isLoading: false })
    } catch (err) {
      set({ error: err.message, isLoading: false })
    }
  }
}))

Pour les flux asynchrones complexes, le guide des patterns async Zustand recommande de garder les états de chargement et d'erreur colocalisés avec les données qu'ils décrivent.

Changements de Zustand v5 susceptibles d'apparaître en entretien

Zustand v5 a introduit des changements majeurs que les recruteurs utilisent pour évaluer si les candidats restent à jour :

  • Plus d'exports par défaut : Importer { create } au lieu de create
  • TypeScript plus strict : Le pattern create<T>()() remplace create<T>()
  • Hydratation persist synchrone : Le timing du callback onRehydrateStorage a changé
  • Abandon du support CJS : Package ESM uniquement

La migration de v4 vers v5 nécessite de mettre à jour les instructions d'import et d'ajuster tout code qui dépendait de l'ancien timing d'hydratation.

Points clés pour les entretiens Zustand

  • Les stores Zustand vivent en dehors de l'arbre React, permettant un accès synchrone et évitant l'imbrication de providers
  • Les sélecteurs avec comparaison superficielle optimisent les re-rendus pour les applications en production
  • Le middleware persist nécessite partialize pour exclure les fonctions du stockage
  • Le pattern slice fait évoluer Zustand vers les grandes applications sans sacrifier la sécurité des types
  • Les actions async fonctionnent directement sans middleware, contrairement aux patterns Redux
  • Les tests nécessitent une réinitialisation explicite de l'état entre les cas de test
  • Les Server Components forcent Zustand aux frontières client, influençant les décisions d'architecture

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Défi du jour

Tu saurais repérer le bug en React / Next.js ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 25 août 2026

Tags

#zustand
#react
#state-management
#interview
#typescript

Partager

Articles similaires