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
// 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;
}
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
// 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 >
);
}
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.