Back to Blog
tips8 min read

React State Management in 2026: Zustand, TanStack Query, and URL State

Choosing the right state management for React — TanStack Query for server state, Zustand for global UI state, URL state for filters, useState for local state.

V
By Ventra Rocket
·Published on 10 February 2026
#React#Zustand#TanStack Query#State Management#Frontend

State management in React is about using the right tool for each type of state — not forcing everything into one solution.

Types of State

| State Type | Examples | Best Tool | |-----------|----------|-----------| | Server state | API data, cache | TanStack Query | | Global UI state | modals, theme | Zustand | | Local UI state | form input, toggles | useState | | URL state | filters, pagination | searchParams | | Form state | validation, submission | React Hook Form |

1. Server State with TanStack Query

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";

function useProducts(category: string) {
  return useQuery({
    queryKey: ["products", category],
    queryFn: () => fetchProducts(category),
    staleTime: 5 * 60 * 1000,  // 5 minutes
    gcTime: 30 * 60 * 1000,    // 30 minutes
  });
}

function useCreateProduct() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: createProduct,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["products"] });
    },
  });
}

2. Global UI State with Zustand

import { create } from "zustand";
import { persist } from "zustand/middleware";

const useUIStore = create()(
  persist(
    (set) => ({
      theme: "dark" as "light" | "dark",
      sidebarOpen: true,
      toggleTheme: () =>
        set((state) => ({
          theme: state.theme === "dark" ? "light" : "dark",
        })),
      toggleSidebar: () =>
        set((state) => ({ sidebarOpen: !state.sidebarOpen })),
    }),
    { name: "ui-store" }
  )
);

// Usage — only re-renders on subscribed slice changes
function Sidebar() {
  const { sidebarOpen, toggleSidebar } = useUIStore();
  return <aside data-open={sidebarOpen}>...</aside>;
}

3. URL State for Shareable Filters

"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback } from "react";

function useProductFilters() {
  const router = useRouter();
  const searchParams = useSearchParams();

  const category = searchParams.get("category") ?? "all";
  const page = Number(searchParams.get("page") ?? 1);

  const updateFilter = useCallback(
    (key: string, value: string) => {
      const params = new URLSearchParams(searchParams.toString());
      params.set(key, value);
      if (key !== "page") params.set("page", "1");
      router.push(`?${params.toString()}`, { scroll: false });
    },
    [router, searchParams]
  );

  return { category, page, updateFilter };
}

4. Form State with React Hook Form + Zod

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const schema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  message: z.string().min(10),
});

function ContactForm() {
  const { register, handleSubmit, formState: { errors, isSubmitting } } =
    useForm({ resolver: zodResolver(schema) });

  return (
    <form onSubmit={handleSubmit(async (data) => submitContact(data))}>
      <input {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}
      <button type="submit" disabled={isSubmitting}>Send</button>
    </form>
  );
}

Anti-Patterns to Avoid

  • useEffect for derived state — compute during render instead
  • Context for high-frequency updates — Context re-renders all consumers
  • Storing server data in useState — use TanStack Query
  • Redux for small apps — adds boilerplate without proportional benefit

Conclusion

Composable state management: TanStack Query for server state, Zustand for global UI, useState for local, URL for shareable, React Hook Form for forms. This combination minimizes re-renders and reduces boilerplate.

Related Articles

React State Management in 2026: Zustand, TanStack Query, and URL State | Ventra Rocket