React Ecosystem and Popular Libraries
1. React Router and Client-side Navigation (v6)
| Feature | API | Example | Use Case |
|---|---|---|---|
| Basic Routing | BrowserRouter, Routes, Route |
<Route path="/about" element={<About />} /> |
Define app routes and components |
| Dynamic Routes | :param syntax, useParams() |
<Route path="/user/:id" /> |
Routes with variable segments |
| Navigation | Link, NavLink, useNavigate() |
<Link to="/about">About</Link> |
Navigate between routes |
| Nested Routes | Outlet, nested Route |
<Route path="dashboard" element={<Layout />}> |
Layout with child routes |
| Protected Routes | Navigate, conditional rendering |
{!auth ? <Navigate to="/login" /> : children} |
Restrict access by auth status |
| Query Params | useSearchParams() |
const [params, setParams] = useSearchParams() |
Read/write URL query strings |
| Lazy Loading | React.lazy(), Suspense |
const About = lazy(() => import('./About')) |
Code-split route components |
Example: React Router v6 setup
// Install
npm install react-router-dom
// App.tsx
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/users">Users</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserProfile />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
// Dynamic route with useParams
function UserProfile() {
const { id } = useParams();
return <div>User Profile: {id}</div>;
}
// Programmatic navigation
function LoginButton() {
const navigate = useNavigate();
const handleLogin = async () => {
await login();
navigate('/dashboard', { replace: true });
};
return <button onClick={handleLogin}>Login</button>;
}
Example: Nested routes and layouts
import { Outlet } from 'react-router-dom';
// Layout component with Outlet
function DashboardLayout() {
return (
<div>
<header>Dashboard Header</header>
<aside>Sidebar</aside>
<main>
<Outlet /> {/* Child routes render here */}
</main>
</div>
);
}
// App with nested routes
function App() {
return (
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
// Protected route wrapper
function ProtectedRoute({ children }) {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
}
// Usage
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardLayout />
</ProtectedRoute>
}
/>
2. State Management Libraries (Redux, Zustand, Jotai, Recoil)
| Library | Key Features | Core API | Best For |
|---|---|---|---|
| Redux Toolkit | Reducers, actions, middleware, DevTools | configureStore, createSlice, createAsyncThunk |
Large apps, complex state, predictable updates |
| Zustand | Minimal, no boilerplate, React-first | create(), set(), get() |
Simple global state, quick setup, small apps |
| Jotai | Atomic state, bottom-up approach | atom(), useAtom() |
Fine-grained reactivity, derived state |
| Recoil | Atoms, selectors, async state | atom(), selector(), useRecoilState() |
Complex derived state, async dependencies |
| MobX | Observable state, automatic tracking | makeObservable, observer, action |
OOP patterns, automatic reactivity |
| Valtio | Proxy-based, mutable syntax | proxy(), useSnapshot() |
Mutable-style state, simple API |
Example: Redux Toolkit setup
// Install
npm install @reduxjs/toolkit react-redux
// store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
// counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface CounterState {
value: number;
}
const initialState: CounterState = { value: 0 };
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1; // Immer makes this safe
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// App.tsx
import { Provider } from 'react-redux';
import { store } from './store';
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
// Counter.tsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
</div>
);
}
Example: Zustand - minimal state management
// Install
npm install zustand
// store.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
interface BearStore {
bears: number;
increase: () => void;
decrease: () => void;
reset: () => void;
}
export const useBearStore = create<BearStore>()(
devtools(
persist(
(set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
decrease: () => set((state) => ({ bears: state.bears - 1 })),
reset: () => set({ bears: 0 }),
}),
{ name: 'bear-storage' }
)
)
);
// Component.tsx
function BearCounter() {
const bears = useBearStore((state) => state.bears);
return <h1>{bears} bears</h1>;
}
function Controls() {
const increase = useBearStore((state) => state.increase);
const decrease = useBearStore((state) => state.decrease);
return (
<div>
<button onClick={decrease}>-</button>
<button onClick={increase}>+</button>
</div>
);
}
// Async actions
interface UserStore {
user: User | null;
fetchUser: (id: string) => Promise<void>;
}
export const useUserStore = create<UserStore>((set) => ({
user: null,
fetchUser: async (id) => {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user });
},
}));
3. UI Component Libraries (MUI, Chakra UI, shadcn/ui)
| Library | Design System | Key Features | Bundle Size |
|---|---|---|---|
| Material-UI (MUI) | Material Design | Comprehensive components, theming, customization, TypeScript | Large (~300KB min+gzip) |
| Ant Design | Enterprise UI | 200+ components, i18n, form validation, charts | Large (~500KB min+gzip) |
| Chakra UI | Accessible, composable | Style props, dark mode, responsive, a11y-first | Medium (~150KB min+gzip) |
| Mantine | Modern, feature-rich | 120+ components, hooks, form management, notifications | Medium (~180KB min+gzip) |
| shadcn/ui | Copy-paste components | Radix UI + Tailwind, full control, no package dependency | Small (only what you use) |
| Radix UI | Headless components | Unstyled, accessible primitives, full control | Small (~50KB per component) |
| Headless UI | Tailwind Labs | Unstyled, accessible, works with Tailwind | Small (~20KB min+gzip) |
Example: Material-UI (MUI) setup
// Install
npm install @mui/material @emotion/react @emotion/styled
// App.tsx
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { Button, Container, Typography, Box } from '@mui/material';
import CssBaseline from '@mui/material/CssBaseline';
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Container>
<Typography variant="h1" component="h1" gutterBottom>
Hello MUI
</Typography>
<Box sx={{ display: 'flex', gap: 2 }}>
<Button variant="contained" color="primary">
Primary
</Button>
<Button variant="outlined" color="secondary">
Secondary
</Button>
</Box>
</Container>
</ThemeProvider>
);
}
Example: Chakra UI setup
// Install
npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion
// App.tsx
import { ChakraProvider, Box, Button, Heading, VStack } from '@chakra-ui/react';
import { extendTheme } from '@chakra-ui/react';
const theme = extendTheme({
colors: {
brand: {
50: '#e3f2fd',
500: '#2196f3',
900: '#0d47a1',
},
},
});
function App() {
return (
<ChakraProvider theme={theme}>
<Box p={8}>
<VStack spacing={4} align="stretch">
<Heading size="xl">Hello Chakra</Heading>
<Button colorScheme="brand" size="lg">
Click Me
</Button>
<Box bg="gray.100" p={4} borderRadius="md">
Responsive Box
</Box>
</VStack>
</Box>
</ChakraProvider>
);
}
Example: shadcn/ui - copy-paste approach
// Install CLI
npx shadcn-ui@latest init
// Add components
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card
// Components are copied to your project
// components/ui/button.tsx
// components/ui/card.tsx
// Usage
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
function App() {
return (
<Card>
<CardHeader>
<CardTitle>Welcome</CardTitle>
</CardHeader>
<CardContent>
<Button variant="default">Click Me</Button>
<Button variant="outline">Outline</Button>
</CardContent>
</Card>
);
}
// Full control - edit copied components directly
// Tailwind-based styling
// No package lock-in
4. CSS-in-JS Libraries (styled-components, Emotion, CSS Modules)
| Solution | Approach | Features | Performance |
|---|---|---|---|
| styled-components | Tagged templates | Automatic vendor prefixing, theming, SSR, TypeScript | Runtime CSS generation |
| Emotion | Tagged templates or object | Fast, flexible, source maps, framework agnostic | Runtime, faster than styled-components |
| Tailwind CSS | Utility classes | JIT compiler, purging, plugins, mobile-first | Zero runtime, build-time |
| CSS Modules | Scoped CSS files | Local scope, composition, type-safe with TypeScript | Zero runtime, standard CSS |
| Vanilla Extract | Zero-runtime CSS-in-TS | Type-safe, theme contracts, build-time extraction | Zero runtime, static CSS |
| Linaria | Zero-runtime CSS-in-JS | Build-time extraction, critical CSS, SSR | Zero runtime, extracted CSS |
| Panda CSS | Build-time atomic CSS | Type-safe, recipes, variants, zero runtime | Zero runtime, optimized output |
Example: styled-components
// Install
npm install styled-components
// Component.tsx
import styled from 'styled-components';
const Button = styled.button`
background: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
opacity: 0.8;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
const Container = styled.div`
max-width: 1200px;
margin: 0 auto;
padding: 20px;
@media (max-width: 768px) {
padding: 10px;
}
`;
// Usage
function App() {
return (
<Container>
<Button primary>Primary</Button>
<Button>Secondary</Button>
</Container>
);
}
// Theming
import { ThemeProvider } from 'styled-components';
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
},
spacing: {
small: '8px',
medium: '16px',
},
};
const ThemedButton = styled.button`
background: ${props => props.theme.colors.primary};
padding: ${props => props.theme.spacing.medium};
`;
function App() {
return (
<ThemeProvider theme={theme}>
<ThemedButton>Themed</ThemedButton>
</ThemeProvider>
);
}
Example: Tailwind CSS with React
// Install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
brand: '#007bff',
},
},
},
plugins: [],
};
// index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
// Component.tsx
function Button({ children, variant = 'primary' }) {
const baseClasses = 'px-4 py-2 rounded font-medium transition';
const variantClasses = {
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
outline: 'border border-blue-500 text-blue-500 hover:bg-blue-50',
};
return (
<button className={`${baseClasses} ${variantClasses[variant]}`}>
{children}
</button>
);
}
// With clsx for conditional classes
import clsx from 'clsx';
function Card({ children, active }) {
return (
<div className={clsx(
'p-4 rounded-lg shadow',
active ? 'bg-blue-100 border-blue-500' : 'bg-white border-gray-200',
'border-2 transition-colors'
)}>
{children}
</div>
);
}
Example: CSS Modules
// Button.module.css
.button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.2s;
}
.button:hover {
opacity: 0.8;
}
.primary {
background: #007bff;
color: white;
}
.secondary {
background: #6c757d;
color: white;
}
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
// Button.tsx
import styles from './Button.module.css';
import clsx from 'clsx';
interface ButtonProps {
variant?: 'primary' | 'secondary';
disabled?: boolean;
children: React.ReactNode;
}
function Button({ variant = 'primary', disabled, children }: ButtonProps) {
return (
<button
className={clsx(styles.button, styles[variant])}
disabled={disabled}
>
{children}
</button>
);
}
// Composition
.base {
padding: 10px;
}
.large {
composes: base;
padding: 20px;
font-size: 18px;
}
5. Form Libraries (Formik, React Hook Form, Zod)
| Library | Key Features | Validation | Best For |
|---|---|---|---|
| React Hook Form | Performance-focused, minimal re-renders, small bundle | Built-in, Yup, Zod, Joi integration | Complex forms, performance-critical apps |
| Formik | Popular, full-featured, field-level validation | Built-in, Yup integration | Standard forms, familiar API |
| Final Form | Framework-agnostic, subscription-based updates | Custom validators, async validation | Flexible form state management |
| Zod | TypeScript-first schema validation | Type inference, parse, transform | TypeScript projects, type-safe forms |
| Yup | Schema-based validation, chainable API | Sync/async validation, custom rules | Form validation, data validation |
| TanStack Form | Framework-agnostic, type-safe, adapters | Custom validators, async, field-level | Modern forms, framework flexibility |
Example: React Hook Form with Zod
// Install
npm install react-hook-form @hookform/resolvers zod
// LoginForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// Define schema
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
rememberMe: z.boolean().optional(),
});
type LoginFormData = z.infer<typeof loginSchema>;
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
});
const onSubmit = async (data: LoginFormData) => {
await loginUser(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="email">Email</label>
<input
{...register('email')}
type="email"
id="email"
/>
{errors.email && <span>{errors.email.message}</span>}
</div>
<div>
<label htmlFor="password">Password</label>
<input
{...register('password')}
type="password"
id="password"
/>
{errors.password && <span>{errors.password.message}</span>}
</div>
<div>
<label>
<input {...register('rememberMe')} type="checkbox" />
Remember Me
</label>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Logging in...' : 'Login'}
</button>
</form>
);
}
Example: Dynamic fields and arrays
import { useForm, useFieldArray } from 'react-hook-form';
const schema = z.object({
name: z.string().min(1),
emails: z.array(
z.object({
value: z.string().email(),
primary: z.boolean(),
})
).min(1, 'At least one email required'),
});
function ProfileForm() {
const { register, control, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: '',
emails: [{ value: '', primary: true }],
},
});
const { fields, append, remove } = useFieldArray({
control,
name: 'emails',
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} placeholder="Name" />
{errors.name && <span>{errors.name.message}</span>}
{fields.map((field, index) => (
<div key={field.id}>
<input
{...register(`emails.${index}.value`)}
placeholder="Email"
/>
<label>
<input
{...register(`emails.${index}.primary`)}
type="checkbox"
/>
Primary
</label>
<button type="button" onClick={() => remove(index)}>
Remove
</button>
</div>
))}
<button type="button" onClick={() => append({ value: '', primary: false })}>
Add Email
</button>
<button type="submit">Submit</button>
</form>
);
}
6. Data Fetching Libraries (SWR, React Query, TanStack Query)
| Library | Features | Key Benefits | Use Case |
|---|---|---|---|
| TanStack Query (React Query) | Caching, refetching, pagination, infinite scroll, mutations | Automatic background updates, optimistic updates, devtools | Complex data fetching, real-time updates |
| SWR | Stale-while-revalidate, focus revalidation, lightweight | Simple API, automatic revalidation, fast | Simple data fetching, real-time apps |
| Apollo Client | GraphQL-focused, caching, subscriptions, local state | GraphQL integration, normalized cache, tooling | GraphQL APIs, complex data graphs |
| RTK Query | Redux Toolkit integration, auto-generated hooks | Redux integration, code generation, TypeScript | Redux apps, REST/GraphQL APIs |
| tRPC | End-to-end type safety, React Query integration | No code generation, type inference, full-stack | TypeScript full-stack apps |
Example: React Query (TanStack Query)
// Install
npm install @tanstack/react-query
// Setup
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
refetchOnWindowFocus: false,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
// Basic query
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{data.name}</div>;
}
// Mutations
import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreatePost() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
return (
<button onClick={() => mutation.mutate({ title: 'New Post' })}>
Create Post
</button>
);
}
// Optimistic updates
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const previousTodos = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
return { previousTodos };
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context.previousTodos);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
Example: SWR - simple data fetching
// Install
npm install swr
// Basic usage
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher);
if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;
return <div>Hello {data.name}!</div>;
}
// Global config
import { SWRConfig } from 'swr';
function App() {
return (
<SWRConfig
value={{
fetcher: (url) => fetch(url).then(res => res.json()),
refreshInterval: 3000,
revalidateOnFocus: true,
}}
>
<Dashboard />
</SWRConfig>
);
}
// Mutations
import useSWRMutation from 'swr/mutation';
async function updateUser(url, { arg }) {
await fetch(url, {
method: 'PUT',
body: JSON.stringify(arg)
});
}
function Settings() {
const { trigger, isMutating } = useSWRMutation('/api/user', updateUser);
return (
<button
disabled={isMutating}
onClick={() => trigger({ name: 'New Name' })}
>
Update User
</button>
);
}
// Pagination
function Projects() {
const [pageIndex, setPageIndex] = useState(0);
const { data } = useSWR(`/api/projects?page=${pageIndex}`, fetcher);
return (
<div>
{data?.projects.map(p => <div key={p.id}>{p.name}</div>)}
<button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
<button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
</div>
);
}
Example: Infinite scroll with React Query
import { useInfiniteQuery } from '@tanstack/react-query';
import { useInView } from 'react-intersection-observer';
function Posts() {
const { ref, inView } = useInView();
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 0 }) => fetchPosts(pageParam),
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
});
useEffect(() => {
if (inView && hasNextPage) {
fetchNextPage();
}
}, [inView, hasNextPage, fetchNextPage]);
return (
<div>
{data?.pages.map((page) => (
<div key={page.nextCursor}>
{page.posts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
))}
<div ref={ref}>
{isFetchingNextPage ? 'Loading more...' : hasNextPage ? 'Load More' : 'Nothing more'}
</div>
</div>
);
}
Ecosystem Integration Tips: Choose libraries based on project needs, bundle size matters,
prefer TypeScript-friendly libraries, check maintenance and community, test library compatibility, use
tree-shaking when available, consider migration path, read documentation thoroughly, start simple then add
complexity, benchmark performance in your app.