MENU
The React Library Ecosystem
React turns your components into a tree of elements and schedules the work of putting that tree on screen. It has no date picker, no chart, no modal, no HTTP client, no design system. That omission is why a real application pulls twenty packages out of npm before it renders its first screen.The good part of the bargain is choice; the bad part is that a dependency is a promise someone else has to keep. Packages every tutorial recommended five years ago are now archived repositories that never got a React 19 release. The survivors fall into four kinds: component kits such as React Bootstrap, which ship finished styled widgets; headless libraries, which ship the behavior and none of the appearance; domain libraries that solve one problem well; and development tools such as Storybook, which never reach users at all.Every version, licence and download number here was read from the npm registry and the GitHub API on 17 September 2026; the next subsection shows the commands.2.19.1 Choosing a Library
Star counts are the worst way to judge a React package: stars accumulate and never expire. Three registry queries tell you more, in ten seconds.npm view react-bootstrap version time.modified peerDependencies license
npm view react-bootstrap dist-tags
curl -s https://api.npmjs.org/downloads/point/last-week/react-bootstrap
version = '2.10.10'
time.modified = '2025-09-22T05:34:15.235Z'
peerDependencies = { react: '>=16.14.0', 'react-dom': '>=16.14.0', ... }
license = 'MIT'
2.19.2 React Bootstrap
Bootstrap is a CSS toolkit plus a JavaScript bundle that makes its interactive pieces work. That JavaScript is the problem in React: Bootstrap's plugins find elements with querySelector, toggle classes on them and keep state in DOM attributes â exactly the mutations React assumes nobody else is making. React Bootstrap rebuilds every widget as a real component whose state lives in React, with no jQuery. You keep the stylesheet, imported once at the entry point.import 'bootstrap/dist/css/bootstrap.min.css'; // npm install react-bootstrap bootstrap
import { Card, Button, Badge } from 'react-bootstrap';
export default function ProductCard({ product, onAdd }) {
return (
<Card style={{ width: '18rem' }}>
<Card.Body>
<Card.Title>{product.name} <Badge bg="success">new</Badge></Card.Title>
<Card.Text className="text-muted">${product.price.toFixed(2)}</Card.Text>
<Button variant="primary" size="sm" onClick={() => onAdd(product.id)}>
Add to cart</Button>
</Card.Body>
</Card>
);
}
2.19.3 Carousel
<Carousel> holds the strip of items, <Carousel.Item> wraps each slide, and <Carousel.Caption> positions text over it. Left alone it advances every five seconds; pass activeIndex and onSelect and the current slide becomes your state, which you need as soon as anything else on the page cares what is showing. The demo runs with no build step: an import map resolves the bare specifiers and @babel/standalone compiles the JSX.<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css">
<div id="root" class="p-3"></div>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<script type="importmap">{"imports": {
"react": "https://esm.sh/react@19.3.0",
"react/jsx-runtime": "https://esm.sh/react@19.3.0/jsx-runtime",
"react-dom": "https://esm.sh/react-dom@19.3.0",
"react-dom/client": "https://esm.sh/react-dom@19.3.0/client",
"react-bootstrap": "https://esm.sh/react-bootstrap@2.10.10?external=react,react-dom"
}}</script>
<script type="text/babel" data-type="module">
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
import { Carousel } from 'react-bootstrap';
const slides = [['Split keyboards', '#1565c0'], ['Studio monitors', '#2e7d32'],
['Braided cables', '#6a1b9a']];
function Gallery() {
const [i, setI] = useState(0);
return (
<Carousel activeIndex={i} onSelect={setI} interval={null}>
{slides.map(([name, color]) => (
<Carousel.Item key={name}>
<div style={{ height: 150, background: color }} />
<Carousel.Caption><h3 className="h5">{name} ({i + 1} of 3)</h3></Carousel.Caption>
</Carousel.Item>))}
</Carousel>);
}
createRoot(document.getElementById('root')).render(<Gallery />);
</script>
Figure 2.62: Browser output of Listing 2.182onSelect receives the index being moved to, so setI alone keeps the
pair in sync. Other props: interval in milliseconds, pause="hover" (the
default) or pause={false}, fade for a cross-fade, controls={false} and
indicators={false} to drop the chrome, and touch for swipes.2.19.4 Tooltip
A tooltip is a label that appears beside an element while it is hovered or focused. React Bootstrap splits the job: <Tooltip> is the bubble, <OverlayTrigger> decides when to show it and where.import { OverlayTrigger, Tooltip, Button } from 'react-bootstrap';
const places = ['top', 'right', 'bottom', 'left'];
export default function Toolbar() {
return places.map((placement) => (
<OverlayTrigger key={placement} placement={placement} delay={{ show: 250, hide: 100 }}
overlay={<Tooltip id={`tip-${placement}`}>On <strong>{placement}</strong></Tooltip>}>
<Button variant="secondary" className="me-2">Tooltip on {placement}</Button>
</OverlayTrigger>
));
}
2.19.5 Button Group
<ButtonGroup> glues buttons into one control: inner borders collapse and only the outer corners stay rounded. Its whole API is size, vertical and an aria-label, and it composes with dropdowns through as, the prop every React Bootstrap component accepts to change what it renders as.The version you reach for more often is <ToggleButtonGroup>, which turns the same row into a radio or checkbox control with state. Each <ToggleButton> needs an id, because it renders a real <input> behind the label, and that hidden input is what makes the group keyboard-navigable and readable by assistive technology.// every component here is a named export of 'react-bootstrap'
<ButtonGroup aria-label="Page size">
<Button variant="outline-secondary">10</Button>
<Button variant="outline-secondary">25</Button>
<DropdownButton as={ButtonGroup} title="More" variant="outline-secondary" id="more">
<Dropdown.Item eventKey="50">50 per page</Dropdown.Item>
</DropdownButton>
</ButtonGroup>
<ToggleButtonGroup type="radio" name="sort" value={value} onChange={onChange}>
<ToggleButton id="s-new" value="new" variant="outline-primary">Newest</ToggleButton>
<ToggleButton id="s-price" value="price" variant="outline-primary">Price</ToggleButton>
</ToggleButtonGroup>
2.19.6 Headless Component Libraries
A component kit gives you behavior and appearance, and you fight the appearance whenever your design differs from the kit's. A headless library ships only the hard half: focus management, keyboard interaction, ARIA roles and states, portals, collision-aware positioning. It renders unstyled elements carrying data attributes such as data-state="open"; you supply every class name.import { Dialog } from 'radix-ui'; // inside a DeleteButton({ onConfirm }) component
<Dialog.Root>
<Dialog.Trigger className="btn-danger">Delete</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="modal">
<Dialog.Title>Delete this product?</Dialog.Title>
<Dialog.Description>This cannot be undone.</Dialog.Description>
<button onClick={onConfirm}>Delete</button>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
2.19.7 Charting
A chart library turns numbers into coordinates and coordinates into marks. Recharts does both with React components: declare <XAxis>, <CartesianGrid> and <Line> inside a chart and it renders SVG you can style.import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
ResponsiveContainer } from 'recharts';
const data = [{ month: 'Jan', orders: 120, returns: 9 },
{ month: 'Feb', orders: 145, returns: 12 }, { month: 'Mar', orders: 131, returns: 7 },
{ month: 'Apr', orders: 188, returns: 14 }];
// the body of an OrdersChart component:
<ResponsiveContainer width="100%" height={280}>
<LineChart data={data} margin={{ top: 8, right: 16, left: 0, bottom: 4 }}>
<CartesianGrid stroke="#eceff1" /><XAxis dataKey="month" /><YAxis /><Tooltip /><Legend />
<Line type="monotone" dataKey="orders" stroke="#1565c0" strokeWidth={2} />
<Line type="monotone" dataKey="returns" stroke="#ef6c00" strokeWidth={2} />
</LineChart>
</ResponsiveContainer>
2.19.8 Animation
The thing CSS cannot do alone is animate an element React is about to unmount: by the time the transition would run, the node is gone. Motion â formerly published as Framer Motion â keeps removed children in the tree until their exit animation finishes.import { motion, AnimatePresence } from 'motion/react';
// the body of a CartLines({ lines, onRemove }) component:
<ul>
<AnimatePresence initial={false}>
{lines.map((line) => (
<motion.li key={line.id} layout
initial={{ opacity: 0, x: -24 }} animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, height: 0 }} transition={{ duration: 0.18 }}>
{line.name} <button onClick={() => onRemove(line.id)}>Remove</button>
</motion.li>
))}
</AnimatePresence>
</ul>
2.19.9 Data Fetching with TanStack Query
Data that lives on a server is not application state. You do not own it, it goes stale without telling you, and two components asking for it at once should not produce two requests. The Effect-based fetch of Section 2.4.4 handles none of that; npm install @tanstack/react-query handles all of it behind two Hooks. One QueryClient holds the cache and one provider puts it in context; each query is identified by a queryKey, an array that doubles as a dependency list â change the key and Query fetches the new entry while showing the old.import { QueryClient, QueryClientProvider, useQuery, useMutation, useQueryClient }
from '@tanstack/react-query';
const client = new QueryClient();
const api = 'https://api.example.com/products';
function Products({ category }) {
const qc = useQueryClient();
const { data, isPending, isError, error } = useQuery({
queryKey: ['products', category], staleTime: 30_000,
queryFn: () => fetch(`${api}?category=${category}`).then((r) => r.json()),
});
const add = useMutation({
mutationFn: (p) => fetch(api, { method: 'POST', body: JSON.stringify(p) }),
onSuccess: () => qc.invalidateQueries({ queryKey: ['products'] }),
});
if (isPending) return <p>Loading...</p>;
if (isError) return <p>{error.message}</p>;
return (<>
<ul>{data.map((p) => <li key={p.id}>{p.name}</li>)}</ul>
<button onClick={() => add.mutate({ name: 'New', category })}>Add</button>
</>);
}
export default () => (
<QueryClientProvider client={client}><Products category="keyboards"/></QueryClientProvider>);
2.19.10 Storybook
Storybook runs your components outside the application: no routes, no store, no login screen, just a component and the props you want to see it with. Each combination is a story; together they form a catalog designers, testers and new team members can browse without running the app.npx storybook@latest init detects Vite and React, writes .storybook/ plus sample stories, and adds the scripts, so npm run storybook opens the catalog. A story file sits next to its component: metadata in the default export, one named export per state.import ProductCard from './ProductCard';
export default {
component: ProductCard,
args: { product: { id: 1, name: 'Split keyboard', price: 89 } },
};
export const Default = {};
export const LongName = {
args: { product: { id: 2, name: 'Programmable 68-key split board', price: 129 } },
};
export const FreeItem = { args: { product: { id: 3, name: 'Sticker pack', price: 0 } } };