Advanced Topics & Ecosystem
Expand skills with routing, state management, and modern tooling
Advanced Topics & Ecosystem
Master React's advanced patterns and ecosystem with free flashcards and spaced repetition practice. This lesson covers performance optimization, code splitting, server-side rendering, state management libraries, testing strategies, and the broader React ecosystemβessential concepts for building production-ready applications.
Welcome to Advanced React π
You've mastered the fundamentals of Reactβcomponents, props, state, and hooks. Now it's time to level up! This lesson explores the advanced patterns and ecosystem tools that separate hobbyist React developers from professionals building scalable, performant applications.
We'll dive deep into performance optimization techniques, explore modern state management solutions beyond useState, understand server-side rendering (SSR) and static site generation (SSG), implement code splitting for faster load times, and navigate the rich ecosystem of libraries that complement React. By the end, you'll have the knowledge to architect enterprise-level React applications.
π‘ Tip: These advanced topics build on each other. Even if you don't need all of them immediately, understanding the landscape helps you make informed architectural decisions.
Core Concepts
1. Performance Optimization β‘
React.memo and useMemo are your first line of defense against unnecessary re-renders. React's rendering is fast, but in complex applications with deep component trees, optimization becomes critical.
React.memo is a higher-order component that memoizes your component, preventing re-renders when props haven't changed:
const ExpensiveComponent = React.memo(({ data }) => {
// Only re-renders if 'data' prop changes
return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>;
});
useMemo memoizes computed values:
const filteredList = useMemo(() => {
return items.filter(item => item.price < maxPrice);
}, [items, maxPrice]); // Only recalculates when dependencies change
useCallback memoizes functions, preventing child components from re-rendering due to new function references:
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // Function reference stays constant
π§ Memory Device: M.C.V. - Memo for components, Callback for functions, useMemo for Values.
Virtual DOM Optimization: React uses a virtual DOM diffing algorithm, but you can help by:
- Using stable
keyprops (never use array indices for dynamic lists) - Keeping component state as local as possible
- Lifting computationally expensive operations outside render
Code Splitting with React.lazy loads components only when needed:
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<HeavyComponent />
</Suspense>
);
}
π‘ Did you know? React.lazy automatically splits your bundle at the component level, but you can also split at the route level using React Router for maximum efficiency!
2. Server-Side Rendering (SSR) & Static Site Generation (SSG) π
Why SSR/SSG Matter:
- SEO: Search engines can index fully-rendered HTML
- Performance: Users see content faster (First Contentful Paint)
- Social Sharing: Meta tags work properly for link previews
Server-Side Rendering renders React components on the server for each request:
// Next.js example
export async function getServerSideProps(context) {
const data = await fetchDataFromAPI();
return { props: { data } }; // Passed to component as props
}
function Page({ data }) {
return <div>{data.title}</div>;
}
Static Site Generation pre-renders pages at build time:
// Next.js example
export async function getStaticProps() {
const data = await fetchDataFromAPI();
return {
props: { data },
revalidate: 60 // Regenerate page every 60 seconds (ISR)
};
}
Incremental Static Regeneration (ISR) combines the best of both worldsβstatic generation with periodic updates.
| Approach | When to Use | Performance | Freshness |
|---|---|---|---|
| CSR (Client-Side) | Authenticated dashboards | Slower initial load | Real-time |
| SSR | Dynamic, SEO-critical pages | Fast FCP | Always fresh |
| SSG | Blogs, documentation | Fastest | Build-time |
| ISR | E-commerce product pages | Very fast | Periodic updates |
π Real-world analogy: Think of SSG as a restaurant with a pre-made buffet (instant service), SSR as cooking each dish to order (fresh but takes time), and CSR as giving customers ingredients to cook themselves (flexibility but slowest).
3. State Management Ecosystem ποΈ
When Context API Isn't Enough: While React's Context API works for simple global state, large applications need more robust solutions.
Redux remains the most popular state management library:
// Redux Toolkit (modern Redux)
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1; },
decrement: state => { state.value -= 1; }
}
});
const store = configureStore({
reducer: { counter: counterSlice.reducer }
});
Zustand offers a simpler API with less boilerplate:
import create from 'zustand';
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 }))
}));
function Counter() {
const { count, increment } = useStore();
return <button onClick={increment}>{count}</button>;
}
Recoil provides atom-based state management from Facebook:
import { atom, useRecoilState } from 'recoil';
const countState = atom({
key: 'countState',
default: 0
});
function Counter() {
const [count, setCount] = useRecoilState(countState);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Jotai is even lighter than Recoil:
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
STATE MANAGEMENT DECISION TREE
βββββββββββββββββββββββββββ
β Need global state? β
ββββββββββ¬βββββββββββββββββ
β
ββββββββ΄ββββββββ
β β
βββ΄ββ βββ΄ββ
βYESβ βNO β
βββ¬ββ βββ¬ββ
β β
βΌ βΌ
βββββββββββββββ ββββββββββββββββ
β Complex app β β Use useState β
β with many β β and props β
β features? β ββββββββββββββββ
ββββββββ¬βββββββ
β
ββββββ΄βββββ
β β
βββ΄ββ βββ΄ββ
βYESβ βNO β
βββ¬ββ βββ¬ββ
β β
βΌ βΌ
βββββββ βββββββββββ
βReduxβ β Context β
βZustandβ β or β
βββββββ β Zustand β
βββββββββββ
4. Testing React Applications π§ͺ
Testing Philosophy: Test behavior, not implementation. Users don't care about state variablesβthey care about what they see and can do.
React Testing Library is the modern standard:
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter on button click', () => {
render(<Counter />);
const button = screen.getByRole('button', { name: /increment/i });
const count = screen.getByText(/count: 0/i);
fireEvent.click(button);
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});
userEvent simulates real user interactions more accurately:
import userEvent from '@testing-library/user-event';
test('types into input field', async () => {
render(<SearchForm />);
const input = screen.getByRole('textbox');
await userEvent.type(input, 'React');
expect(input).toHaveValue('React');
});
Testing Hooks with renderHook:
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';
test('increments counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
Integration Testing with MSW (Mock Service Worker):
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/user', (req, res, ctx) => {
return res(ctx.json({ name: 'John' }));
})
);
beforeAll(() => server.listen());
afterAll(() => server.close());
π§ Try this: Write tests that describe user stories: "As a user, when I click the submit button, I should see a success message."
5. Advanced Patterns π¨
Higher-Order Components (HOCs) wrap components to add functionality:
function withAuth(Component) {
return function AuthenticatedComponent(props) {
const { user } = useAuth();
if (!user) return <LoginPrompt />;
return <Component {...props} user={user} />;
};
}
const ProtectedPage = withAuth(Dashboard);
Render Props pattern:
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return render(position);
}
// Usage
<MouseTracker render={({ x, y }) => (
<h1>Mouse at ({x}, {y})</h1>
)} />
Compound Components pattern (like HTML's <select> and <option>):
const TabContext = createContext();
function Tabs({ children }) {
const [activeTab, setActiveTab] = useState(0);
return (
<TabContext.Provider value={{ activeTab, setActiveTab }}>
{children}
</TabContext.Provider>
);
}
function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}
function Tab({ index, children }) {
const { activeTab, setActiveTab } = useContext(TabContext);
return (
<button
className={activeTab === index ? 'active' : ''}
onClick={() => setActiveTab(index)}
>
{children}
</button>
);
}
function TabPanel({ index, children }) {
const { activeTab } = useContext(TabContext);
return activeTab === index ? <div>{children}</div> : null;
}
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;
// Usage
<Tabs>
<Tabs.List>
<Tabs.Tab index={0}>Home</Tabs.Tab>
<Tabs.Tab index={1}>Profile</Tabs.Tab>
</Tabs.List>
<Tabs.Panel index={0}>Home content</Tabs.Panel>
<Tabs.Panel index={1}>Profile content</Tabs.Panel>
</Tabs>
Error Boundaries catch JavaScript errors in component trees:
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// Usage
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
β οΈ Note: Error Boundaries only catch errors in rendering, lifecycle methods, and constructors. They don't catch errors in event handlersβuse try/catch for those.
6. React Ecosystem Tools π οΈ
Build Tools:
- Vite: Lightning-fast dev server using native ES modules
- Create React App: Official starter (being phased out for Next.js)
- Webpack: Powerful bundler with extensive plugin ecosystem
Styling Solutions:
- Tailwind CSS: Utility-first CSS framework
- Styled-Components: CSS-in-JS with tagged templates
- Emotion: Another CSS-in-JS library with better performance
- CSS Modules: Scoped CSS without runtime overhead
UI Component Libraries:
- Material-UI (MUI): Comprehensive Material Design components
- Chakra UI: Accessible, composable components
- Ant Design: Enterprise-grade UI library
- Radix UI: Unstyled, accessible primitives
- Headless UI: Unstyled components from Tailwind team
Form Libraries:
- React Hook Form: Performant forms with minimal re-renders
- Formik: Popular form library with validation
import { useForm } from 'react-hook-form';
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email', { required: true, pattern: /^\S+@\S+$/i })} />
{errors.email && <span>Valid email required</span>}
<button type="submit">Submit</button>
</form>
);
}
Data Fetching:
- TanStack Query (React Query): Caching, synchronization, and more
- SWR: React Hooks for data fetching by Vercel
- Apollo Client: GraphQL client
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json())
});
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <div>{data.name}</div>;
}
Animation Libraries:
- Framer Motion: Declarative animations
- React Spring: Physics-based animations
- GSAP: Professional-grade animation library
import { motion } from 'framer-motion';
function AnimatedBox() {
return (
<motion.div
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Hello!
</motion.div>
);
}
Examples with Explanations
Example 1: Performance Optimization with React.memo and useCallback π¨
Problem: A parent component re-renders frequently, causing unnecessary re-renders of child components.
// β Inefficient: Child re-renders every time Parent renders
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const handleClick = () => {
console.log('Button clicked');
};
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<ExpensiveChild onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
</div>
);
}
function ExpensiveChild({ onClick }) {
console.log('ExpensiveChild rendered');
// Expensive computation
const result = complexCalculation();
return <button onClick={onClick}>{result}</button>;
}
Why it's inefficient: Every time you type in the input (changing name), ExpensiveChild re-renders even though its props appear unchanged. This is because handleClick is recreated on every render, giving it a new reference.
Solution:
// β
Optimized: Child only re-renders when necessary
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// Memoize the callback so it doesn't change on every render
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // Empty dependency array means it never changes
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<ExpensiveChild onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
</div>
);
}
// Memoize the entire component
const ExpensiveChild = React.memo(({ onClick }) => {
console.log('ExpensiveChild rendered');
const result = complexCalculation();
return <button onClick={onClick}>{result}</button>;
});
Key Takeaway: Use React.memo for components and useCallback for functions passed as props. Now ExpensiveChild only re-renders when its props actually change, not when Parent's other state updates.
Example 2: Code Splitting with React.lazy and Route-Based Splitting ποΈ
Problem: Your app's initial bundle is 2MB, making the first load painfully slow.
// β Everything loads upfront
import Dashboard from './Dashboard';
import Settings from './Settings';
import Profile from './Profile';
import AdminPanel from './AdminPanel';
function App() {
return (
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
);
}
Solution with lazy loading:
// β
Load components only when routes are visited
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
const Profile = lazy(() => import('./Profile'));
const AdminPanel = lazy(() => import('./AdminPanel'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
</Suspense>
);
}
function LoadingSpinner() {
return (
<div className="spinner">
<div className="bounce1"></div>
<div className="bounce2"></div>
<div className="bounce3"></div>
</div>
);
}
Result: Initial bundle drops to ~200KB. Each route's code loads on-demand. Users on the dashboard never download admin panel code!
Advanced: Prefetching:
// Prefetch a route when user hovers over a link
function NavigationLink({ to, children }) {
const prefetch = () => {
// Trigger the lazy import without rendering
import('./Dashboard');
};
return (
<Link to={to} onMouseEnter={prefetch}>
{children}
</Link>
);
}
Example 3: Custom Hook for Data Fetching with TanStack Query π
Problem: Every component fetches data differently, with inconsistent loading states and caching.
// β Manual data fetching in every component
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user.name}</div>;
}
Solution with TanStack Query:
// β
Declarative, cached, auto-refetching data
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Custom hook for user data
function useUser(userId) {
return useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
},
staleTime: 5 * 60 * 1000, // Data fresh for 5 minutes
cacheTime: 10 * 60 * 1000, // Cache for 10 minutes
});
}
// Custom hook for updating user
function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ userId, data }) => {
const response = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return response.json();
},
onSuccess: (data, variables) => {
// Invalidate and refetch user query
queryClient.invalidateQueries({ queryKey: ['user', variables.userId] });
}
});
}
// Component using the hooks
function UserProfile({ userId }) {
const { data: user, isLoading, error } = useUser(userId);
const updateUser = useUpdateUser();
const handleNameChange = (newName) => {
updateUser.mutate({ userId, data: { name: newName } });
};
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div>
<h1>{user.name}</h1>
<button onClick={() => handleNameChange('New Name')}>Update</button>
</div>
);
}
Benefits:
- β Automatic caching: Navigate away and backβno refetch needed
- β Background refetching: Data stays fresh
- β Deduplication: Multiple components requesting same data make one request
- β Optimistic updates: UI updates before server responds
- β Error retry logic built-in
Example 4: Server-Side Rendering with Next.js π
Problem: Your React SPA has poor SEO and slow initial load times.
// β Client-side only: Bad for SEO
function BlogPost() {
const [post, setPost] = useState(null);
const { id } = useParams();
useEffect(() => {
fetch(`/api/posts/${id}`)
.then(r => r.json())
.then(setPost);
}, [id]);
if (!post) return <div>Loading...</div>;
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
Solution with Next.js SSR:
// β
Server-side rendered: Great for SEO
// File: pages/posts/[id].js
export async function getServerSideProps(context) {
const { id } = context.params;
// This runs on the server for every request
const response = await fetch(`https://api.example.com/posts/${id}`);
const post = await response.json();
return {
props: { post } // Passed to component
};
}
function BlogPost({ post }) {
// post is already available on first render!
return (
<article>
<Head>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<meta property="og:title" content={post.title} />
<meta property="og:image" content={post.image} />
</Head>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
export default BlogPost;
Even better with Static Generation:
// β
Static generation with ISR: Best of both worlds
export async function getStaticPaths() {
// Generate paths for the 100 most popular posts at build time
const response = await fetch('https://api.example.com/posts/popular');
const posts = await response.json();
const paths = posts.map(post => ({
params: { id: post.id.toString() }
}));
return {
paths,
fallback: 'blocking' // Other posts generated on first request
};
}
export async function getStaticProps(context) {
const { id } = context.params;
const response = await fetch(`https://api.example.com/posts/${id}`);
const post = await response.json();
return {
props: { post },
revalidate: 60 // Regenerate page every 60 seconds if requested
};
}
function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
export default BlogPost;
Result:
- β Pages served as static HTML (instant load)
- β Automatic regeneration keeps content fresh
- β Perfect for SEO (search engines see full HTML)
- β Social media preview cards work correctly
Common Mistakes β οΈ
1. Over-optimizing Too Early
// β Premature optimization
const MyComponent = React.memo(({ text }) => {
const memoizedValue = useMemo(() => text.toUpperCase(), [text]);
const handleClick = useCallback(() => console.log(text), [text]);
return <div onClick={handleClick}>{memoizedValue}</div>;
});
Why it's wrong: This component is so simple that the optimization overhead costs more than the re-render. React is fastβoptimize when you measure a problem, not preemptively.
Better:
// β
Keep it simple until you need optimization
function MyComponent({ text }) {
return <div onClick={() => console.log(text)}>{text.toUpperCase()}</div>;
}
2. Forgetting Dependencies in useMemo/useCallback
// β Stale closure: count is always 0
function Counter() {
const [count, setCount] = useState(0);
const logCount = useCallback(() => {
console.log(count);
}, []); // Missing dependency!
return <button onClick={logCount}>Log Count: {count}</button>;
}
Fix: Always include all dependencies from the component scope:
// β
Proper dependencies
const logCount = useCallback(() => {
console.log(count);
}, [count]);
3. Not Handling Loading and Error States in Data Fetching
// β Incomplete: What if the fetch fails?
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(r => r.json())
.then(setUsers);
}, []);
return users.map(u => <div key={u.id}>{u.name}</div>);
}
Fix: Always handle loading, error, and empty states:
// β
Complete state handling
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(r => r.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <Spinner />;
if (error) return <div>Error: {error}</div>;
if (users.length === 0) return <div>No users found</div>;
return users.map(u => <div key={u.id}>{u.name}</div>);
}
4. Improper Error Boundary Placement
// β Single error boundary catches too much
function App() {
return (
<ErrorBoundary>
<Navbar />
<MainContent />
<Sidebar />
<Footer />
</ErrorBoundary>
);
}
Problem: If Navbar crashes, the entire app disappears!
Fix: Use granular error boundaries:
// β
Isolated error boundaries
function App() {
return (
<>
<ErrorBoundary fallback={<SimpleNav />}>
<Navbar />
</ErrorBoundary>
<ErrorBoundary fallback={<ErrorMessage />}>
<MainContent />
</ErrorBoundary>
<ErrorBoundary fallback={<div>Sidebar unavailable</div>}>
<Sidebar />
</ErrorBoundary>
<Footer />
</>
);
}
5. Mixing SSR and Client-Only APIs
// β Crashes during SSR: window is undefined on server
function Component() {
const width = window.innerWidth;
return <div>Width: {width}</div>;
}
Fix: Check if you're on the client:
// β
Safe for SSR
function Component() {
const [width, setWidth] = useState(0);
useEffect(() => {
setWidth(window.innerWidth);
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return <div>Width: {width}</div>;
}
Key Takeaways π―
Performance: Don't optimize prematurely. Profile first, then use React.memo, useMemo, and useCallback strategically.
Code Splitting: Use React.lazy for route-based splitting. Your users shouldn't download code they'll never use.
SSR/SSG: Choose based on your content:
- SSG for static content (blogs, docs)
- SSR for dynamic, SEO-critical pages
- CSR for authenticated dashboards
- ISR for the best of both worlds
State Management: Start simple (useState/Context), upgrade when needed (Zustand/Redux). Don't reach for Redux on day one.
Testing: Test behavior, not implementation. Use React Testing Library and write tests users would understand.
Error Handling: Always use Error Boundaries, and make them granular. One component's crash shouldn't take down your entire app.
Ecosystem: The React ecosystem is vast. Choose tools that solve your actual problems, not what's trendy.
Data Fetching: Consider TanStack Query or SWR instead of managing loading/error states manually everywhere.
π‘ Final Tip: The React ecosystem moves fast. Focus on understanding the underlying patterns (composition, unidirectional data flow, declarative UI) rather than memorizing specific APIs. Patterns stay relevant; APIs change.
π Further Study
- React Official Documentation - Advanced Guides
- Patterns.dev - React Patterns
- TanStack Query Documentation
π Quick Reference Card: Advanced React Essentials
| Concept | Tool/Pattern | When to Use |
|---|---|---|
| Performance | React.memo, useMemo, useCallback | Expensive renders, deep trees, frequent updates |
| Code Splitting | React.lazy + Suspense | Large bundles, route-based splitting |
| SSR | Next.js getServerSideProps | SEO-critical, dynamic content |
| SSG | Next.js getStaticProps | Blogs, docs, mostly static content |
| State Management | Zustand / Redux Toolkit | Complex global state, large apps |
| Data Fetching | TanStack Query | REST APIs, caching, background sync |
| Testing | React Testing Library | Unit & integration tests |
| Error Handling | Error Boundaries | Graceful failure, error recovery |
| Forms | React Hook Form | Complex forms, validation |
| Animation | Framer Motion | UI transitions, micro-interactions |
Optimization Decision Tree:
1. Is there a performance problem? β NO β Don't optimize
β YES
2. Profile with React DevTools
β
3. Identify expensive components
β
4. Apply targeted optimization:
- React.memo for component
- useMemo for expensive calculations
- useCallback for stable function references
Memory Aid - M.C.V.: Memo components, Callback functions, useMemo Values