An exhaustive React ecosystem for 2020
This is a continuously evolving curation of my favorite technologies for creating modern React front-end applications. There are hundreds of tools, libraries, and frameworks available; this is my attempt to consolidate some of my favorites to develop with.
Why not npm?
A huge advantage of yarn over npm is workspaces, which lets you organize your code in a single Git repository into many modules which can be published independently or symlinked to each other. This enables you to create small, isolated, and independently tested and versioned pieces of your application.
Do I need Lerna?
Lerna is another mono-repository tool but I feel like yarn workspaces now encompasses most of the necessary functionality provided by Lerna out of the box.
Like yarn workspaces, Bit is another tool for organizing your code into modules. However, Bit takes it to the next level, letting you share your modules seamlessly with other repositories. Where normally you might publish a separate library of components to share among your project repositories, Bit sort of decentralizes this. You can edit and publish your modules in any of the repos you use them in and then sync those changes in your other projects. It’s like combining yarn workspaces with Git submodules.
Docker is a powerful tool for building and running your projects in a VM-like setup. With Docker you can avoid having to deal with third-party packages that are sometimes incompatible with OS software updates on your local computer. You can also run a single command to spin up all of the dependencies of your project in separate containers, like your API server and databases. This more closely emulates your production environment, and irons out inconsistencies in the platforms other developers on your team work on locally.
Parcel.js is a modern code bundler and a faster, mostly configuration-free alternative to Webpack. While it doesn’t have the same community yet, I’ve had nothing but pleasant experiences working with Parcel.js. It’s fully capable of compiling and transpiling all different kinds of assets, is incredibly speedy, has code-splitting built in, it even has hot module replacement. You don’t need to configure Babel, Webpack, PostCSS, or other tools over and over again, nor rely on create-react-app to set up hundreds of lines of boilerplate. Want to add Sass?
yarn add -d sass and you’re done!
While Parcel.js is a bundler for consuming your source files to produce an application build, rollup.js is ideal for preparing your source to publish as a package. It’s designed to produce ES modules, CommonJS modules, and more.
React.useState, React.useMemo, React.useCallback, React.useRef, and React.useEffect
React hooks are the latest way to enhance your React components with state, references, and more. They’re a functional alternative to the class lifecycle, and are much more composable. Among the most useful are
Many other third-party libraries are available that build more complex hooks off these primitive ones. One of my favorites is react-use, which includes dozens of frequently needed hooks. Before rolling your own, check to see if react-use already has what you need.
I also recommend the higher-order component react-hooks-compose. It allows you to write pure, stateless React components that are trivial to test without testing behavior, while separating your hooks concerns so that they can be unit-tested independently as well. It’s much easier to test pure functions with data input/output!
While you want to be careful not to overuse memoization since it can consume your users’ browser memory,
memo is helpful for preventing unnecessary re-renders of components when they are expensive or re-render to frequently. It’s a higher-order component that stores a copy of the last set of props passed to the component and compares them before deciding whether to call the component’s
render function again.
React.suspense and React.lazy
suspense API enables you to create components with behavior when they are awaiting asynchronous code, like external API calls, in a natural and consistent way.
lazy complements it by providing a native React method to lazy-load your components. If you implement code-splitting in your application, you can load small bundles on page load and request more source files on demand.
The styled-components package provides components and higher-order components for adding CSS to your React components. While I previously enjoyed CSS modules, I am starting to prefer styled-components. They are very composable; they provide a natural way to apply different styles based on a component’s props rather than adding classes conditionally; they support theming; they play nicely with animations. Essentially, they allow you to apply actual logical programming to the CSS of your application.
Some argue that Redux is no longer necessary with the new React context API. I disagree. Redux provides a way to create a global state for your entire application to share a single copy of many of your resources. Your state tree can become quite complicated, but it is trivial to create components that subscribe to particular slices of your store, and with function composition and the
useSelector hook, you can transform your normalized state into dumbed-down structures for your components. Testing selectors independently and writing simple, straightforward components is a very nice developer experience.
Redux also comes with a rich ecosystem of other modules. While many people criticize it for reducers that feel like they have a lot of repetitive boilerplate, it really comes down to how you design your action creators and reducers. Done well, Redux applications are easy to test, behave predictably, have cleanly separated concerns, and come equipped with developer tools that make it a breeze to debug by observing a history of dispatched actions, stepping back in time, and exporting state from production to quickly replicate bugs in a local environment.
Reselect is a beautiful library to supplement Redux. You can create selectors, also known as lenses in functional programming, to convert your normalized store state into something more useful for your components. The API is designed to make selectors composable into more complex selectors without becoming repetitive, and testing them independently as well as in tandem.
Context used to have a clunky, unintuitive API, but it has been rewritten. The context API allows you to share data and callbacks with any descendant components of its provider. Instead of always passing props down each level of a component tree, context lets you subscribe at any level of the hierarchy and receive data from the nearest provider above it.
Context is simpler than Redux and does not enforce you to dispatch actions and update state with reducers. A downside is that any component that subscribes to a context instance will re-render if any part of that context changes, rather than selecting only the specific slices of state it cares about. With that in mind, context is better-suited to small, specific pieces of data. Think of an accordion component, whose children are many expandable panels, and opening one will collapse the other currently open panel. Context is a more reliable and cleaner alternative than
I choose context when the state is local to the component and its descendants — like a user’s visual preferences or the state of a modal window — and Redux when the state is independent of any particular component — such as representations of entities in a database, which any component could be interested in consuming.
While the most popular module for application routes for React is react-router, I love Wouter. It’s tiny, fully embraces hooks, and has an intuitive and barebones API. I can accomplish everything I could with react-router with Wouter, and it just feels more minimalist while not being inconvenient.
Formik is my new go-to library of choice for building forms in React — something that no application goes without. It has a nice, concise, hooks-based API for form state. Write simple validation functions and just spread the props to your normal HTML form element components. You don’t have to read the documentation on an entirely new set of components, integrate with Redux, etc. Even though it’s simple, it’s powerful enough to let you work with non-trivial data structures and plays well with any validation library.
seapig is a utility for creating composable React components. It’s often preferable to leverage the children prop with React, since you can create many small components rather than large components with dozens of props that render many elements and are complex to use as well as test. seapig has a dead-simple API for letting you choose where and how to use the children passed to a component, like injecting them into the right spots in your markup.
This one is a no-brainer. core-js is the easiest way to provide all the polyfills you need to write modern JS code that will work in all browsers. It’s much nicer to be able to use the latest and greatest native APIs on the Array prototype or work with Maps and Sets instead of importing libraries that implement their own non-standard solutions. Less Lodash!
Gatsby is more of a framework in and of itself, so it can feel a bit like vendor lock-in. That said, it’s a wonderful experience if you want to create progressive web apps. Gatsby provides tools to pre-render static websites from React, making your app blazing fast without server-side rendering. It has hundreds of plugins which help you build your website or app by pulling from all kinds of data sources programmatically, at build time, producing a complete set of files to deploy directly to static hosting like S3 or Netlify. Any part of your app can consume data both statically and dynamically with GraphQL, and your build will have intelligent code-splitting based on your routes with no configuration necessary — making your site powerful and as fast as it can possibly be!
AVA is a minimalist test runner, built with a clean API you can read from top to bottom in minutes, but without sacrificing the most useful parts of a testing framework. AVA runs tests in parallel, so it’s very fast and enforces atomicity. It’s equipped with some very nice formatting for test results output. Each of your test suites is executed in its own context, so you don’t have to worry about environments messing with each other. It’s an “opinionated” test runner, but with opinionated libraries come straightforward conventions, and I don’t have any qualms with the decisions they’ve made.
The contemporary alternative to Enzyme, colloquially known as React Testing Library or RTL, this lightweight library enforces good testing for React. It doesn’t allow you to test implementation details — and while that can feel like it’s a pain, the confidence your tests will provide more than makes up for it. If you design your components with forethought, testing with RTL should not be hard. RTL always renders your components just like if they’d been rendered in the DOM, and provides the actual DOM API for asserting that things rendered correctly, along with a few useful helpers.
The dynamic-hoc module wraps any React higher-order component and allows you to replace it with a different HOC at runtime.
React apps that use Redux are likely to have many components connected to the store using the
connect HOC. You might want to test your Redux action creators and reducers independently of your components — which is why it’s a common pattern to export both your presentational component as well as your connected component from a module file. However, when switching from Enzyme to RTL, “shallow” rendering (i.e. rendering a component without rendering its children in a unit test) is no longer possible.
This means that if any of the descendants of the component you want to test are connected to the Redux store, there’s no getting around mocking the store in order to test the components, regardless of whether the root component you are testing is isolated from the store.
I created dynamic-hoc to allow you set up your components so that your tests can replace any of the HOCs of child components. Instead of mocking data that needs to be translated by selectors, you can replace the HOCs of the child components with simple versions that inject static prop values or derive them in some other way.
ESLint and its many plugins and presets work in tandem with testing to help you write good code. Some lint rules simply enforce code style, while others can help you find “code smell” — from debug logs you forgot to remove to potentially dangerous exploits. ESLint is critical to any JS project.
Prettier is a code formatter tool. Eliminate endless arguments over code style or worse, inconsistencies in the files and functions written by different developers on your team. Prettier reformats your entire code base (or just the files you’ve changed in your latest commit, if used with a tool like Husky) according to a simple configuration file that dictates your preferred code style.
Cypress is an automated test runner for building end-to-end tests — that is, tests that actually simulate a user interacting with your app via a browser (specifically, Chrome). Cypress is a more developer-friendly alternative to Selenium, though it has its drawbacks.
There’s probably hundreds more utilities, frameworks, and libraries I haven’t yet discovered or thought to add to this list, so hopefully it will continue to grow — both as my personal reference and hopefully as a useful collection for other React developers! Please, enlighten me in the comments with more packages and tools to aggregate here.