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.
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
Next.js App Router: Patterns for Production
A practical guide to building fast, scalable web apps with the Next.js App Router — server components, streaming, parallel routes, intercepting routes, and caching strategies.
10 TypeScript Patterns for Cleaner Code
A practical collection of 10 TypeScript patterns — from discriminated unions and template literal types to the satisfies operator — that make your codebase more type-safe and maintainable.
Web Security Essentials: OWASP Top 10 for Node.js and React
Practical security — preventing SQL injection, XSS, broken authentication, and IDOR in Node.js applications.