If you are starting out with React, the sheer number of libraries that are used alongside it can be overwhelming. In this post, I will try to provide a short summary of them so that you can orient yourself better. I won't be able to list every single library here but I hope that knowing the landscape will help you know what is out there.
(This post was also translated into Korean)
A quick note before we dive in
Consider whether you really need a library before adopting it. While a library can save you time initially, it can cost you time in the future as you need to work around its bugs and limitations.
10,000ft view
To keep things organized, I will group the libraries by their purpose.
Logic
This section contains the libraries that are used to implement the behavior of an app.
State management
The simplest way to use state in React is with the useState hook. This state is only accessible within the component in which the hook is declared and as an app grows, you will need to share state between multiple components. While you could share state with only React hooks by either "lifting the state up" or storing the state in context, these approaches can be problematic. After lifting the state up, you need to pass the value through multiple layers of components if you need its value in a grandchild component. React context is not intended to be used for state management and used without care, will cause most of the app to re-render. This is where state management libraries come in, to manage state that needs to be shared between multiple components.
State management with the Flux pattern
The oldest and most common way to manage state in React is to store the entire state in a single "store" and feed it to components while updating it with separate actions. This is the flux pattern that Facebook introduced as a early guideline for managing state in React and the most popular state management library Redux uses this pattern. Due to how early versions of Redux required setting up several files, it developed a reputation in the community for being "heavy" or "full of boilerplate". But with the release of Redux toolkit, the amount of configuration required was reduced with the utilities it provided. Zustand has become more popular with the community lately because it implements the flux pattern with a few custom hooks.
Proxy state management
An early alternative to the flux pattern was proxy state management where state was stored in a JS proxy that can detect when it was changed and re-render the React components that are tied to it. Unlike store based state management which has immutable state, the state managed by proxy state management libraries are mutable. MobX is the most known state management library that follows this pattern and Valtio is simpler take on it based with a few hooks.
Atomic state management
With atomic state management, you define pieces of state called atoms that can then be imported and used by several components. Recoil and Jotai are the most well known examples of this pattern.
State machines
In some cases like working with multi stage animations, we need to change state based on the state of other components or at timed intervals or even prevent it from changing under certain conditions. While these state transitions can be handled with a combination of useState or useEffect hooks, it leads to code that is hard to understand as the logic gets spread across several hooks. This is where adopting finite state machines can really help and the best known finite state machine library for Javascript is xstate which has the dedicated @xtate/react package with hooks and utilities for it to be used with React.
Data fetching
Data fetching, that is downloading data from a server, is one of the most common tasks in a Frontend App. The basic way to do this is with the built in Fetch API but many prefer using something like Axios to avoid the Fetch's API awkwardness. However, as the app grows it needs to download more data, handle more loading and error states and do things like refetching, caching and optimistic changes to improve user experience. This is really tricky to implement on your own which is where data fetching libraries can help.
TanStack Query (previously known as React Query) and SWR both provide React hooks to fetch data and handle refetching and caching. If you are using Redux, RTK query is addon for doing data fetching with it.
All of the previous libraries are intended to be used with REST APIs. A common issue with REST APIs is that they have a fixed data structure which can't be changed without modifying the backend code. GraphQL aims to solve that problem by allowing the frontend to choose the exact data it wants. Apollo Client is the most common implementation of GraphQL used with React and provides a lot of data fetching features out of the box.
Utility function libraries
Javascript provides a whole bunch of built in methods for manipulating arrays and objects. But these methods can be tedious to use for certain operations such as iterating over object keys, flattening nested data structures or immutably cloning deeply nested structures. This is where utility function libraries like Lodash can be useful with their utility functions for common operations.
If you prefer writing functional JS, lodash also has a fp variant which is immutable and supports currying. Ramda is an alternative utility function library that is functional first.
Client side routing
In the early days of the web, every webpage was a separate html file with its own URL. With the rise of frontend frameworks, single page applications (SPA) became popular where a single webpage would mimic the feel of multiple pages by using Javascript to show different content. In order to allow a user to navigate to a specific view in SPA, client side routing was born where JS would read the URL and show different contents while using the History API to change the URL in the browser without loading a separate html file.
React router is the de-facto library for client side routing in React and it tackles parsing URLs and manipulating the browser history so that you can simply specify URLs and the components that must be shown for them.
Authentication
Most web apps are multi-tenant and work with multiple users at the same time. To achieve this, each user must prove their identity and usually, this is done with passwords or social logins where the user's identity is proven by another party. This is authentication and implementing it requires a common set of functionality such as creating users, signing in/out and resetting passwords. Services like auth0's universal login provide a lot of this functionality for you that can be easily integrated into a React app with their SDK Another option is SuperTokens which is open source and can be self hosted.
Form helpers
React re-renders all child components by default when state is changed. This means that in the case of a form with several controlled inputs, a change to a single input will cause the entire form to re-render. Libraries like React Hook Form help avoid this pitfall and provide a simpler interface for doing input validation. Another alternative is React Final Form, the successor to Redux form which is one of the earliest form helpers for React.
Schema validation
Validation is where user input is checked before it is accepted into a system to reduce the risk of incorrect / malicious data from entering it. Validating several inputs within a nested data structure can lead to convoluted code. This is where schema validation libraries such as yup and zod can help. They let you declare the validation rules declaratively which is much easier to understand than custom validation functions.
Internationalization
There are thousands of languages and many different time, date, number and currency formats used across the world. Internationalization (frequently abbreviated as i18n) is the process of making an application capable of using different languages and formats. This involves changing the text and the data formats used in an app based on user settings. While you could implement this yourself, libraries like react-i18next and FormatJS provide ready-made code and established conventions for doing internationalization.
Date libraries
Javascript dates are weird. Additionally, formatting dates, parsing them from a string or manipulating them by increasing or decreasing time units is very cumbersome with plain JS. This is where date libraries and the helper functions they provide for working with dates can help. MomentJS used to be the most popular and capable date library but it is not actively being developed anymore and the creators of Moment recommend Luxon instead as it is similarly capable and improves on moment in several ways (immutable, supports tree shaking, builds on native Intl). date-fns or Day.js can be a better option for simpler use cases.
Styling
This section contains the libraries that help with the styling of an app. Since styling on the web is done with CSS, the following libraries provide easier ways to work with CSS.
CSS in JS
We often want our React components to have a different style based on the state that it is in. Doing this with only React involves changing the style prop or changing classes. While libraries like classnames or the smaller clsx make it easier to work with class name strings, it is still tedious to keep the class names in sync across JS and CSS. This is where CSS in JS libraries come in, allowing you to declare CSS alongside your components in JS which can be easily changed based on state or props with interpolation. emotion and styled components are the most used CSS in JS libraries.
CSS utilities
Usually we write CSS from scratch, starting with an empty file and adding more rules as we go along. But often we need to create components that have a similar visual appearance. In this case, you can duplicate the CSS rules into a new selector, reuse the existing CSS selector or create a new generic CSS selector that can be applied to all components that need the same appearance. Adam Wathan argues that neither option is good and proposes using a combination of pre-defined styles that can be mixed and matched to achieve the desired appearance. Tailwind CSS implements this approach.
Components
This section contains entire React components that can be added to your project so that you don't have to implement their behavior or styling on your own.
Component libraries
Sometimes called UI libraries, these packages provide a collection of commonly used pieces of UI as components. Libraries like MUI, Ant Design, Chakra UI and Mantine are pre-styled, with each following a common design aesthetic so that all the components look like they belong together. Radix UI provides components with no styling so that you can implement your own.
Individual components
Listing all the individual React component packages here will be impossible. So I will list a few that tackle some common concerns.
File uploads
The native way for dealing with files on the web is to use the file input
elements or file drag and drop with the File API. Implementing common UX for file upload on the web, especially around uploading images, can be cumbersome with these low level APIs. Enter filepond and uppy which provide components for working with file uploads.
Drag and Drop (DnD)
When it comes to working with files or re-organizing a list of items, drag and drop is a much better user experience. Plain HTML drag and drop involves marking elements as draggable and listening to the drag events to handle what should happen with the dragged element. Libraries like React DnD and dnd kit provide pre-made components and conventions to handle most drag and drop use cases without having to write your own code for it.
Animation
Doing animation on the web usually involves CSS transitions or CSS animations. Handling this in React can be tedious as each element's animation state has to be maintained and appended as inline styles or classnames. This is what libraries like react-spring and Framer Motion try to address by exposing components where animation can be defined in a more straightforward way. They also attempt to make the animations feel more natural with built in animation easing.
In addition, entrance and especially exit animations when a component mounts or unmounts in React is also tricky to implement which is why React transition group exists to simply it.