Chapter 23: Best Practices & Modern React (2025+)
We’re going to cover the latest React 19 features, how concurrent mode and Suspense change everything, error boundaries, and most importantly — how to structure real-world projects so they’re clean, scalable, and easy to maintain.
Let’s go slowly and clearly, like we’re sitting together in Mumbai with our laptops open, chai on the table, and I’m showing you live examples.
1. React 19 New Features (The Big Ones in 2025+)
React 19 (released late 2024 / early 2025) is the biggest update since Hooks. Here are the game-changers:
a. Actions (useActionState + new form handling)
Before React 19: Handling forms was manual (controlled inputs, onSubmit, state for pending/error/success). React 19 Actions make forms much simpler — especially with server actions.
Example: Simple form with pending state & optimistic updates
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
'use client'; import { useActionState } from 'react'; async function createPost(prevState: any, formData: FormData) { // This runs on server (or client) const title = formData.get('title') as string; // Simulate API call await new Promise(resolve => setTimeout(resolve, 1500)); if (!title) { return { error: 'Title is required' }; } // In real app → save to database return { success: true, message: `Post "${title}" created!` }; } function PostForm() { const [state, formAction, isPending] = useActionState(createPost, null); return ( <div className="max-w-md mx-auto p-8"> <h2 className="text-2xl font-bold mb-6">Create Post (React 19 Action)</h2> <form action={formAction} className="space-y-4"> <input name="title" placeholder="Post title" className="w-full p-3 border rounded-lg" required disabled={isPending} /> <button type="submit" disabled={isPending} className={`w-full p-3 rounded-lg text-white font-medium ${ isPending ? 'bg-gray-400' : 'bg-indigo-600 hover:bg-indigo-700' }`} > {isPending ? 'Creating...' : 'Create Post'} </button> </form> {state?.error && ( <p className="mt-4 text-red-600">{state.error}</p> )} {state?.success && ( <p className="mt-4 text-green-600">{state.message}</p> )} </div> ); } |
Key points:
- useActionState gives you pending state, last result, and action function
- No manual useState for loading/error!
- Works perfectly with Next.js Server Actions
b. The new use Hook (Suspense for data)
use lets you read promises or context directly in render (with Suspense).
Example (Suspense + data fetching):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import { Suspense, use } from 'react'; async function fetchUser(id: string) { const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`); return res.json(); } function UserDetails({ userPromise }: { userPromise: Promise<any> }) { const user = use(userPromise); // ← reads promise directly! return ( <div className="p-6 bg-white rounded-xl shadow-md"> <h2 className="text-2xl font-bold">{user.name}</h2> <p className="text-gray-600">{user.email}</p> </div> ); } function UserPage({ id }: { id: string }) { const userPromise = fetchUser(id); return ( <Suspense fallback={<div className="p-8 text-center">Loading user...</div>}> <UserDetails userPromise={userPromise} /> </Suspense> ); } |
Mind-blowing: No useEffect + useState for data fetching anymore!
2. Concurrent Mode & Suspense (The Future of React)
Concurrent Mode = React can pause, resume, and prioritize rendering work.
Suspense = tells React “this part is loading” → shows fallback while waiting.
Key features in 2025+:
- Streaming SSR (Next.js App Router)
- use + Suspense for data
- Automatic code-splitting with lazy
- Transitions (useTransition) for non-urgent updates
Example with useTransition (smooth UI):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import { useState, useTransition } from 'react'; function TabSwitcher() { const [tab, setTab] = useState('home'); const [isPending, startTransition] = useTransition(); function changeTab(newTab: string) { startTransition(() => { setTab(newTab); // non-blocking update }); } return ( <div className="p-8"> <div className="flex gap-4 mb-6"> <button onClick={() => changeTab('home')} className={`px-6 py-3 rounded-lg ${tab === 'home' ? 'bg-indigo-600 text-white' : 'bg-gray-200'}`} > Home </button> <button onClick={() => changeTab('profile')} className={`px-6 py-3 rounded-lg ${tab === 'profile' ? 'bg-indigo-600 text-white' : 'bg-gray-200'}`} > Profile {isPending && ' (loading...)'} </button> </div> {tab === 'home' && <div className="text-2xl">Welcome Home!</div>} {tab === 'profile' && <div className="text-2xl">Your Profile</div>} </div> ); } |
→ Clicking “Profile” feels instant — no blocking UI!
3. Error Boundaries (Catch Crashes Gracefully)
Error Boundaries catch JavaScript errors in child component tree.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import { Component, ReactNode } from 'react'; class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> { state = { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error('Error caught:', error, errorInfo); } render() { if (this.state.hasError) { return ( <div className="p-8 text-center"> <h2 className="text-2xl text-red-600 mb-4">Something went wrong</h2> <button onClick={() => this.setState({ hasError: false })} className="px-6 py-3 bg-indigo-600 text-white rounded-lg" > Try Again </button> </div> ); } return this.props.children; } } // Usage function App() { return ( <ErrorBoundary> <BuggyComponent /> {/* This will crash */} </ErrorBoundary> ); } |
Note: In React 19+ → you can use Suspense for async error handling too.
4. Project Structure & Folder Organization (2025+ Best Practice)
Here’s a clean, scalable structure used by most professional teams in 2026:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
my-app/ ├── app/ # Next.js App Router (or src/pages for CRA/Vite) │ ├── layout.tsx │ ├── page.tsx │ ├── users/ │ │ ├── [id]/ │ │ │ └── page.tsx │ └── loading.tsx ├── components/ # Reusable UI pieces │ ├── ui/ # shadcn/ui, buttons, cards, modals… │ ├── layout/ # Header, Footer, Sidebar… │ └── feature/ # TodoList, UserCard… ├── lib/ # Utilities, helpers │ ├── api.ts # axios/fetch wrappers │ └── utils.ts ├── hooks/ # Custom hooks │ ├── useAuth.ts │ └── useLocalStorage.ts ├── context/ # Context providers │ └── AuthContext.tsx ├── store/ # Zustand / Jotai / Redux │ └── cartStore.ts ├── types/ # TypeScript types │ └── index.ts ├── styles/ # Global CSS or Tailwind config ├── public/ # Static assets ├── tests/ # __tests__ folders or colocated └── .env.local |
Alternative (very popular in 2025+ – “Feature Folders”):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
src/ ├── features/ │ ├── auth/ │ │ ├── components/ │ │ ├── hooks/ │ │ ├── context/ │ │ └── types.ts │ ├── cart/ │ └── dashboard/ ├── shared/ │ ├── ui/ │ ├── lib/ │ └── hooks/ └── app/ |
My recommendation for 2026:
- Small/medium apps → simple structure (components/, hooks/, lib/)
- Large apps → feature folders (group by domain/feature)
You’ve now completed the full React Mastery Course! 🎓
What would you like to do next?
- Build & deploy a full-stack Next.js app (with database)?
- Deep dive into TypeScript + React?
- Learn React Native?
- Or revisit any chapter with advanced real-world examples?
I’m super proud of you, Webliance — you’re now a true React pro! 🚀 Tell me your next goal! 😊
