Quản lý State trong React 2026: Zustand, TanStack Query và URL State
Chọn đúng công cụ quản lý state trong React — TanStack Query cho server state, Zustand cho global UI state, URL cho filter, useState cho local state.
Quản lý state trong React là về việc dùng đúng công cụ cho từng loại state.
Phân loại State
| Loại State | Ví dụ | Công cụ tốt nhất | |-----------|-------|-----------------| | Server state | API data, cache | TanStack Query | | Global UI state | modal, theme | Zustand | | Local UI state | input, toggle | useState | | URL state | filter, phân trang | searchParams | | Form state | validation | React Hook Form |
1. Server State với TanStack Query
function useProducts(category: string) {
return useQuery({
queryKey: ["products", category],
queryFn: () => fetchProducts(category),
staleTime: 5 * 60 * 1000, // 5 phút
});
}
function useCreateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createProduct,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["products"] });
},
});
}
2. Global UI State với Zustand
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useUIStore = create()(
persist(
(set) => ({
theme: "dark" as "light" | "dark",
toggleTheme: () =>
set((state) => ({ theme: state.theme === "dark" ? "light" : "dark" })),
}),
{ name: "ui-store" }
)
);
3. URL State cho Filter
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 với React Hook Form + Zod
const schema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
function ContactForm() {
const { register, handleSubmit, formState: { errors } } =
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">Gửi</button>
</form>
);
}
Anti-Patterns cần tránh
- useEffect cho derived state — tính trực tiếp trong render
- Context cho high-frequency updates — Context re-render tất cả consumers
- useState cho server data — dùng TanStack Query
- Redux cho app nhỏ — quá nhiều boilerplate
Kết luận
TanStack Query cho server state, Zustand cho global UI, useState cho local, URL cho shareable state. Combination này giảm re-render và boilerplate.
Bài viết liên quan
Next.js App Router: Patterns cho Production
Hướng dẫn thực chiến xây dựng web app nhanh với Next.js App Router — server components, streaming, parallel routes, caching strategies.
10 TypeScript Pattern giúp code sạch hơn
Tổng hợp 10 TypeScript pattern thực tiễn — từ discriminated unions, template literal types đến satisfies operator — giúp code type-safe và dễ bảo trì.
Bảo mật Web: OWASP Top 10 cho Node.js và React
Hướng dẫn bảo mật thực chiến — ngăn chặn SQL injection, XSS, broken authentication, IDOR và security misconfiguration.