React Libraries

The React Ecosystem, Grouped by the Job Each Library Does

React itself is small. Everything that makes a React application hard lives in the libraries around it — routing, server state, forms, styling and the build. This is what each group is for, when it earns its place, and what goes wrong once it is in production.

What Turns Up in Real React Codebases

React Router TanStack Query SWR Redux Toolkit Zustand Jotai React Hook Form Zod Tailwind Radix TanStack Table Framer Motion Vite
React component and data-fetching code open on a developer's screen
How to Read This Page

A Library Is a Trade, Not a Badge

Every library here solves a real problem and every one of them costs something: a dependency to keep current, a mental model the next developer has to learn, and a set of failure modes that only appear under load or on a slow connection. Nothing on this page is a recommendation to install anything.

We deliberately do not quote version numbers, download counts or benchmarks. Those move, and a page that repeats them is wrong within months. What does not move is what each tool is for, the situation in which it is worth its weight, and the way teams get it wrong.

Hire React developers
Data and State

Where Most of the Difficulty Actually Is

Routing, server state, client state and forms. Four decisions that shape everything built on top of them.

Routing

  • React Router — the default for a standalone single-page app, and the one most developers already know. Its data-loading APIs pull fetching out of components, which is a real improvement if you adopt them consistently.
  • TanStack Router — typed routes and typed search parameters. It earns its place when URL state is part of your product, not decoration: filters, pagination and tabs that have to survive a refresh and a shared link.
  • The Next.js router — you do not choose it, the framework does. The cost is that routing, data fetching, caching and rendering become the same decision.

What goes wrong: state that belongs in the URL is kept in a component instead, so the back button, a refresh and a pasted link all behave differently from what the user expects. The other one is a router upgrade treated as a version bump when the data APIs have actually changed shape.

Server State

  • TanStack Query — caching, deduplication, background refetching, retries, pagination and optimistic updates. If your app reads from an API more than trivially, this is usually the highest-value dependency in the whole list.
  • SWR — smaller and more opinionated, and enough when the requirement is really just fetch, cache and revalidate.
  • Framework loaders — server components and route loaders move part of this problem to the server, which changes the shape of the answer rather than removing the question.

What goes wrong: treating server data as client state. Once API responses are copied into a global store, the team ends up hand-writing cache invalidation, request deduplication and retry logic, and getting it subtly wrong. In our experience that single decision is behind more React bugs than any other — stale screens, double submissions, and data that differs between two tabs.

Client State

  • Often none of them — when server data is in a query cache and form data is in a form library, what remains is frequently a handful of flags that context or component state covers.
  • Zustand — a small store with no ceremony. The usual right answer when you do need shared client state.
  • Jotai — atom-based, and a good fit when many small independent pieces of state are read by widely separated components.
  • Redux Toolkit — still the right call for genuinely complex client state with many writers, auditable transitions or time-travel debugging. Plain Redux without the toolkit is a red flag on a new codebase.

What goes wrong: a global store adopted on day one for an app that never needed one, so every feature is written with three times the indirection it warranted. The opposite failure is real too: prop drilling six levels deep because nobody wanted to add a dependency.

Forms

  • React Hook Form — uncontrolled by default, so typing in one field does not re-render the entire form. The usual choice for anything with more than a few inputs.
  • Formik — still present in a lot of existing codebases and perfectly workable. Rarely the choice for something new.
  • Zod or Yup — schema validation that the form library plugs into. Zod has the advantage that the schema can also be the TypeScript type and can be reused on the server.

What goes wrong: validation rules written twice, once in the browser and once in the API, and quietly drifting apart until a form accepts something the backend rejects. Close behind: a large form rebuilt on every keystroke, which nobody notices on a fast laptop and everybody notices on a mid-range phone.

Interface and Build

Styling, Tables, Motion and the Toolchain

The parts a user actually sees, plus the machinery that puts them there.

UI and Styling

  • Tailwind — utility classes resolved at build time. Fast to work in once a team is used to it, and it removes the naming problem entirely. The markup gets noisy, which is a genuine trade rather than a fatal one.
  • CSS Modules — scoped plain CSS with no runtime and no new syntax. Unfashionable and still an excellent default.
  • styled-components and runtime CSS-in-JS — styles computed from props, paid for with work on every render and extra setup for server-rendered pages. Worth it for genuinely dynamic styling, expensive as a house style.
  • Radix and headless components — behaviour and accessibility without visual opinions. This is where dialogs, menus, comboboxes and tooltips should come from rather than being written from scratch.
  • MUI — a complete design system out of the box. Excellent when you accept its look, painful when you fight it.
  • shadcn/ui — components copied into your repository rather than installed. You own them and can change anything, and you also own keeping them current.

What goes wrong: three styling systems in one codebase because each new feature picked its own, and hand-rolled dropdowns and modals that trap focus incorrectly and are unusable with a keyboard or a screen reader.

Tables and Virtualisation

  • TanStack Table — sorting, filtering, grouping and column logic with no markup of its own. The right tool once a table stops being a list and starts being a feature.
  • react-window and friends — render only the rows on screen. Necessary once a list is long enough that the browser struggles, not before.
  • Server-side paging — frequently the real answer. If ten thousand rows reach the browser at all, the question is why.

What goes wrong: virtualisation added to paper over a payload that should have been paginated, which breaks in-page search, keyboard navigation and printing. Also common: sorting and filtering done in the browser over a partial dataset, so the results are confidently wrong.

Animation

  • Framer Motion — the default when animation is more than a CSS transition: layout animation, shared element transitions, gesture-driven movement and orchestrated sequences.
  • Plain CSS transitions — still correct for hover states, fades and anything that does not need to be interruptible.

What goes wrong: animation on properties that force layout on every frame, so a page that feels smooth on a desktop stutters on a phone. And forgetting reduced-motion preferences entirely, which for some users is not a preference but a requirement.

Build Tooling

  • Vite — the default for anything that is not tied to a framework's own build. Fast start-up, straightforward configuration, and a sensible production build.
  • Turbopack — the Next.js build path, so it is chosen for you along with the framework.
  • Webpack — what your existing project probably uses. Working Webpack does not need replacing on principle; a thousand-line config nobody understands does.

What goes wrong: nobody owns the bundle. A date library, an icon set imported in full and three copies of the same dependency at different versions, and no one notices until the first-load size is discussed in a meeting. Migrating a build is worth doing, but it is a project — see legacy modernisation.

Failure Modes

What We Find When We Open a React Codebase

None of these are exotic. They are the same handful of problems in different clothes.

API responses stored in a global store, with hand-written cache invalidation slowly diverging from reality.
Every list item re-rendering because a new object or callback is created inline on each parent render.
Effects used to synchronise state that could have been derived, producing loops nobody can trace.
Loading and error states handled in some components and silently missing in others.
Two state libraries in one repository because a migration was started and never finished.
Tests bound to implementation detail, so a harmless refactor turns the suite red and the suite gets ignored.

If that list reads like your repository, a fixed-scope code audit is usually a cheaper first move than hiring into a situation nobody has mapped yet.

Vetting

What We Look For in a Developer Who Lists These on a CV

A list of libraries tells us nothing on its own. These are the questions that separate use from understanding.

Can They Argue Against It

Anyone can praise the library they like. We ask where it would be the wrong choice. A developer who cannot name a situation where their favourite tool loses has not used it in anger.

Cache Behaviour, Not Cache Syntax

What happens when two components request the same data at once, when a window regains focus, when an optimistic update fails. These are the questions that decide whether a dashboard shows the truth.

Render Reasoning

Given a list that stutters, do they reach for memoisation everywhere, or do they find the prop that changes identity on every render? One of those is a fix and the other is noise.

Types That Carry Weight

Whether the schema, the form type and the API response type are one definition or three copies. We look for people who make invalid states unrepresentable rather than decorating code with annotations.

Accessibility as Default

Focus management in a dialog, keyboard operation of a menu, labels that are actually associated with inputs. Developers who reach for headless primitives rather than rebuilding these usually know why.

Fitting In, Not Rewriting

We hire for your codebase, not an ideal one. A senior developer who joins a Formik and Redux project and writes good Formik and Redux is worth more than one who opens a migration pull request in week one.

FAQ

Questions We Get About the React Stack

Do I need a state management library at all?

Often not. Once server data lives in a query cache and form state lives in the form library, what is left is usually a theme toggle, a sidebar flag and the current user — which React context or a fifty-line Zustand store handles fine. Reach for Redux Toolkit when you genuinely have complex client-side state with many writers, undo, or logic that needs to be inspectable in a devtool. Adding a global store before you have that problem is the most common piece of unnecessary architecture we see in React codebases.

Why do you treat server state as a separate concern from client state?

Because it behaves differently. Server data is a cached copy of something you do not own: it goes stale, another user can change it, the same record can be requested by three components at once, and a failed write has to be rolled back. Client state has none of those properties. Codebases that store API responses in Redux end up hand-writing caching, deduplication, retries and invalidation — badly — which is exactly the work TanStack Query or SWR already does. Most of the React bugs we are asked to look at trace back to that one decision.

Is Tailwind or styled-components the better choice?

They solve the same problem with different costs. Tailwind and CSS Modules resolve at build time, so there is no styling work left for the browser at runtime, and both survive server rendering without extra machinery. Runtime CSS-in-JS such as styled-components buys you dynamic styles computed from props, and pays for it with a runtime cost on every render and extra setup for server-rendered output. Neither is wrong. What matters is that the codebase picks one and does not end up with three, which is the state we most often find.

Should a new React project use Next.js or Vite?

If the pages need to be indexed, shared as links or rendered fast on a cold visit, use a framework with server rendering and take its router with it. If the product sits behind a login and nothing about it needs to be crawled, a Vite single-page app is simpler, faster to build and far easier to reason about. The mistake is adopting a server-rendering framework for an internal dashboard and then spending months fighting the parts of it you never needed.

How do you test whether a developer really knows these libraries?

We put them in a codebase that already uses several of them and ask them to fix something real. A list of libraries on a CV tells us almost nothing. What tells us something is whether a candidate can explain why a query refetches when the window regains focus, what a stale cache entry does to an optimistic update, or why a form re-renders on every keystroke — and whether they can say when a library they like would be the wrong call.

Tell Us Which of These You Actually Use

Send us the stack as it really is, including the parts you are not proud of. You get two or three matched developer profiles back within three business days.