Start with useState and Context, and move to Zustand around 3-4 contexts; swapping Redux for Zustand on two projects cut about 2000 lines and fixed 12 bugs.
Keep server data out of client state and let TanStack Query cache it; a 5-minute staleTime is fine for most data.
Use React Hook Form with Zod so one schema validates on both frontend and backend; Formik's last real update was 2021.
Questions people ask
Do I need Redux or Zustand for React state management?
Start with useState and Context, which covers most needs. Move to Zustand when Context gets messy, around 3-4 contexts. Keep Redux Toolkit for complex async flows, when time-travel debugging matters, or when your team already knows Redux.
TanStack Query vs SWR vs RTK Query
Use TanStack Query. It has more features and better DevTools than SWR. RTK Query only makes sense if you're already in Redux.
Is Formik still maintained?
Clint says Formik's last real update was in 2021. He uses React Hook Form instead. It uses uncontrolled inputs, which are faster, and needs no providers or wrapper components.
Should I use React Router or Next.js for a new React project?
For new projects in 2024, use Next.js 14 with App Router, even for SPAs. If you can't use Next.js, use TanStack Router. Clint keeps React Router for legacy projects and migrated three apps away from it this year.
The React ecosystem in 2024: 90% noise, 10% signal
I'm tired of "React best practices" articles written by people who've built three todo apps.
Here's what I actually use after shipping React to millions of users this year. More importantly: what I stopped using and why.
Routing: Just Use Next.js Already
The Only Three Options That Matter
1. Next.js App Router ✅
Stop debating. If you're starting a new project in 2024, use Next.js 14 with App Router. Yes, even for "SPAs."
Why? Because:
Server Components actually work now
Built-in data fetching that doesn't suck
Route-based code splitting for free
SEO without the PhD
// This is all you need. No setup. No config hell.// app/products/[id]/page.tsxexportdefaultasyncfunctionProduct({ params }){const product =awaitgetProduct(params.id)return<ProductView product={product}/>}
2. Tanstack Router 🤔
Only if you absolutely can't use Next.js. It's what React Router should have been. Type-safe routes, built-in search params handling, actually good.
3. React Router ❌
Legacy projects only. v6 broke everything good about v5. The docs still suck. Loader patterns are half-baked.
I migrated three apps away from React Router this year. Zero regrets.
Stop Calling React Query a Router
React Query (TanStack Query) isn't routing. It's data fetching. Different problem. Great library, wrong category.
State Management: You Probably Don't Need Redux
The Real Hierarchy (Use in This Order)
1. useState + Context 🎯
You don't need a library for state management. You need to learn React.
// This handles 80% of "state management" needsconstThemeContext=createContext()constCartContext=createContext()constUserContext=createContext()// That's it. You're done.
2. Zustand ✅
When Context gets messy (around 3-4 contexts), use Zustand. Not before.
// Entire store in 10 linesconst useStore =create((set)=>({user:null,cart:[],setUser:(user)=>set({ user }),addToCart:(item)=>set((state)=>({cart:[...state.cart, item]})),
No providers. No boilerplate. No DevTools extension breaking your Chrome.
3. Redux Toolkit 😔
Only if:
You have complex async flows
Time-travel debugging actually matters
Your team already knows Redux
You hate yourself
I removed Redux from two projects this year. Replaced with Zustand. Code deleted: ~2000 lines. Bugs fixed: 12.
The State Management Lies
"You need Redux for large apps" - False. Shopify uses Zustand. Netflix uses Zustand.
"Redux DevTools are essential" - I haven't opened them in 2 years. console.log works fine.
"Normalized state is critical" - For 99% of apps, just fetch fresh data. Your users have gigabit internet.
Server State: There's Only One Choice
TanStack Query. Period.
Stop evaluating options. Use TanStack Query (formerly React Query).
// This replaces 500 lines of useEffect garbagefunctionProducts(){const{ data, error, isLoading }=useQuery({queryKey:['products'],queryFn: fetchProducts,staleTime:5*60*1000,// 5 minutes})if(isLoading)return<Skeleton/>if(error)return<Error/>return< productsdata
RTK Query only makes sense if you're already trapped in Redux. Otherwise it's like buying a forklift to move a couch.
Why Not SWR?
SWR is fine. TanStack Query is better. More features, better DevTools, actually maintained.
The Server State Rules
Never store server data in client state - That's what the cache is for
staleTime is your friend - 5 minutes is fine for most data
Optimistic updates > loading spinners - Users don't care about your "data integrity"
// Optimistic update that actually worksconst mutation =useMutation({mutationFn: updateProduct,onMutate:async(newProduct)=>{await queryClient.cancelQueries(['products'])const previous = queryClient.getQueryData(['products']) queryClient.setQueryData(['products'],old=> old.map(p=> p.id=== newProduct.id? newProduct : p
Forms: React Hook Form or GTFO
Formik is Dead
Literally. Check the GitHub. Last real update: 2021. Still using Formik? You're maintaining abandonware.
React Hook Form: The Only Option
// This is the entire form logicconst{ register, handleSubmit,formState:{ errors }}=useForm()constonSubmit=async(data)=>{awaitsaveUser(data)}return(<form onSubmit={handleSubmit(onSubmit)}><input {...register('email',{required:true})}/>{errors span requiredspan
No providers. No wrapper components. No 10KB of validation schemas.
Validation: Zod + RHF
Stop writing validation twice. Use Zod schemas for both frontend and backend: