R
Interview Deckreact · next.js · senior/staff
0·0·122
System Design

State Management para Apps Complexos (Ride-sharing)

Gerenciar estado de uma corrida com múltiplas telas, real-time updates, e sincronização entre cliente/servidor.

Um app de ride-sharing tem estado complexo: estado da corrida (requesting → matching → en-route → arrived → in-ride → completed), posição do motorista (atualiza a cada 2s), histórico de corridas, estado de pagamento, notificações. State management precisa lidar com: atualizações frequentes sem re-renders excessivos, persistência offline, sincronização com servidor, e estado derivado (ETA calculado da posição atual).

Problema
Estado da corrida em useState espalhado em múltiplos componentes: posição do motorista re-renderiza o mapa E o ETA E o header ao mesmo tempo. Ou: store global com todo o estado da corrida causa re-renders em componentes não relacionados.
Solução
Separar estado por frequência de atualização: posição do motorista (alta frequência) em Zustand com selector granular; estado da corrida (baixa frequência) em React Query com SSE; estado de UI (modais, loading) em useState local. Usar Zustand stores por feature. Estado derivado (ETA) calculado com useMemo ou Zustand computed getters.
Tip
O maior erro em apps complexos é um único store global. Separar por domínio: `useRideStore`, `useDriverStore`, `usePaymentStore`. Cada store tem sua frequência de atualização. O mapa só subscreve ao `useDriverStore(s => s.position)` — não re-renderiza quando o status da corrida muda.
// Zustand com seletores granulares — evita re-renders
import { create } from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'

interface DriverStore {
  position: { lat: number; lng: number } | null
  eta: number | null // em minutos
  status: 'offline' | 'en-route' | 'arrived'
  updatePosition: (pos: { lat: number; lng: number }) => void
}

export const useDriverStore = create<DriverStore>()(
  subscribeWithSelector((set, get) => ({
    position: null,
    eta: null,
    status: 'offline',
    updatePosition: (pos) => set({ position: pos }),
  }))
)

// Componentes subscrevem apenas ao que precisam
function DriverMap() {
  // Só re-renderiza quando position muda
  const position = useDriverStore(s => s.position)
  return <MapMarker position={position} />
}

function ETABadge() {
  // Só re-renderiza quando eta muda
  const eta = useDriverStore(s => s.eta)
  return <span>{eta ? `${eta} min` : '—'}</span>
}

// SSE para atualizar o store em tempo real
function useRideUpdates(rideId: string) {
  useEffect(() => {
    const es = new EventSource(`/api/ride/${rideId}/stream`)
    es.addEventListener('driver-position', (e) => {
      const pos = JSON.parse(e.data)
      useDriverStore.getState().updatePosition(pos) // imperativo, sem hook
    })
    es.addEventListener('ride-status', (e) => {
      // atualizar React Query cache
      queryClient.setQueryData(['ride', rideId], JSON.parse(e.data))
    })
    return () => es.close()
  }, [rideId])
}
Q.Como evitar re-renders quando a posição do motorista atualiza a cada 2s?
A.Seletores granulares no Zustand: `const position = useDriverStore(s => s.position)` — só re-renderiza quando `position` muda. O mapa subscreve só à position. O ETA subscreve só ao ETA calculado. O header subscreve só ao status. Sem selector: `useDriverStore()` re-renderiza em qualquer mudança de qualquer campo. Com Jotai: atoms granulares por campo — position atom, status atom, etc.
Q.Como sincronizar estado client-side com o servidor em tempo real?
A.React Query + SSE/WebSocket: o servidor publica eventos; o client subscreve e chama `queryClient.setQueryData` para atualizar o cache imediatamente (sem refetch). A query key é a fonte da verdade: qualquer componente que usa `useQuery(['ride', id])` recebe a atualização. Para mutations: optimistic update (ver concept 117). O benefício: React Query gerencia loading, error, e stale state automaticamente.
Q.Como persistir o estado da corrida se o usuário matar o app?
A.Zustand middleware persist: `create(persist(store, { name: 'ride-state', storage: createJSONStorage(() => localStorage) }))`. Configurar `partialize` para persistir só o necessário (não posições transitórias). Na reabertura do app, verificar com o servidor se a corrida ainda está ativa via API (`GET /ride/current`). Se sim, restaurar estado e reconectar SSE. Se não, limpar estado local.
121/122← →