Dec 04, 2022

Building delightful React applications has never been easier

React has been out in the open for almost a decade now, and the ecosystem has grown exponentially. Pretty much every large company uses React for parts of their stack, and few competing frameworks have come close to attracting a similar market share.

I think there are two reasons for the sustained dominance of React. For starters, it feels easy to learn (I really enjoy JSX over the custom templating systems other frameworks use) and once you’re up to speed, it has a vast ecosystem of libraries that make building applications easier than ever.

In the following, I’ll go over a few examples I recently had to deal with for building Anzu.

Starting React projects

The first step of every application built with React is to decide the “distribution” or “flavor” of React. For client-heavy applications, it doesn’t get quicker than create-react-app. The downsides are that for more complex projects, you might have to find workarounds for configuring libraries like Babel that don’t involve ejecting the entire codebase.

If you’re expecting to build lots of static pages and would like to make use of server-side rendering (especially with the new server components), Next.js is an obvious choice with an active community, championed by a well-funded company.

Anecdotally, I’ve used Gatsby in the past but ran into too many problems in part caused by unmaintained packages and a constant feeling of uncertainty with the vision of Gatsby as a whole. It might have gotten better recently, I wouldn’t know.

Fetching data

Chances are that you need to fetch data from your backend. If you’re not using server components yet or have to rely on fetching data from the client (as most dashboard-like projects do), there are many ways to get started.

Inspired by Remix and maintained by its creators, react-router has recently added data loading capabilities, which I haven’t gotten to try as I’m a firm believer of separating data fetching and navigation on the client (maybe I’ll change my mind after giving it a spin).

If you are like me, you might enjoy swr, a simple library by Vercel that makes data-fetching with React hooks straightforward and simplifies invalidation and automatic retries. Fetched results can be cached in localStorage or any other browser store by setting a cache provider. Even if it’s just glorified long-polling, there’s a certain magic in using regular React hooks and having your dashboards update in real time. swr abstracts the right amount of complexity and gives you all the power you need.

When you’re using different interface types like GraphQL, there may be dedicated libraries and frameworks (e.g. Apollo) for handling data-fetching in a type-safe way. tRPC is pretty flashy too and provides you with a type-safe RPC interface.

A small piece of advice that I feel is appropriate for this section is that you should think about the entities consuming your APIs before you decide on API frameworks on the server and data-fetching libraries on the client. If you already know that you have a web application, mobile applications, and external developers, using Next.js API routes may set you up with future issues when you want to separate your frontend and backend.

Designing complex interactions

State management is a dreaded topic in the React sphere. I’ve seen other frameworks provide better primitives that lead to fewer discussions about how we should store view state, so React still has some areas to catch up on.

I think it all comes down to understanding the mental models around state that React provides, and I’ll focus on functional components with hooks: state hooks are useful for local state that may only be passed down the component tree. Attached to local storage, you can even add persistence.

Context can be used as an escape hatch when you need to pass state down multiple levels or even up the tree. Context works against the ideas of dependency injection, so you might have a harder time testing components that rely on context compared to regular state props. On the other hand, there are some places (like remembering global settings or the current account) where context is easier and less verbose than classic state.

Most applications can probably go a long way just using state hooks and context where absolutely necessary, but if you need more complex flows, you’ve probably come across libraries like zustand or jōtai, which are recent alternatives to Redux and mobx.

If you’re more on the state machine side of things, xstate is all the rage right now.

For my part, I think that there are too many ways to handle state, and sometimes, we can distract ourselves from working on meaningful problems and features by debating what I would consider insignificant details. Just start with React hooks, and maybe choose one library that seems like it handles your case if you feel like you need more, then move on and use it until you truly understand your constraints.

Adding interactivity with animations

Continuing with a brighter topic, applications (targeted at consumers) are adopting an increasing number of animations that make even minimal interactions delightful. Framer Motion makes smooth animations easy, as does react-spring and, even though it’s not quite an animation library, react-transition-group.

Building accessible components

Most projects, especially enterprise SaaS products are made up of the same primitives: forms, dropdowns, dialogs, tabs, context menus, tables, you name it. Writing accessible components takes time and effort, and as always, there’s a library for that. Well, multiple.

Radix UI by WorkOS is used by most products you love and gives you unstyled, accessible components ready to use. You just have to bring the styling.

If you’re not quite sure about design and need something to get started with, libraries like Chakra UI provide all the components you need.

If you’re just beginning to build React applications, I think it’s nice to pick some defaults to worry about your problem at hand, but for more ambitious products that need to stand out, you’re probably better off figuring out a design system and using a library like Radix to get you off the ground.

Laying out content with tables and boards

Once you’re working with data, you might want to render different layout types more suitable than regular lists. TanStack Table (formerly known as React Table) gives you a headless (read: only including the unstyled logic) table system for you to render your data, as well as offering pagination, resizing, filtering, sorting, column visibility, and whatever else you may need to deliver a great table experience.

Building boards with drag and drop (think Trello or kanban boards in your issue tracker of choice) is mostly taken care of by using a library like dnd kit, react-dnd, or react-beautiful-dnd (by Atlassian but unfortunately put on hold).

I think one of the biggest factors that make React as popular today as it was five years ago is that most problems are solved many times over now and we’ve seen a suite of robust tools be developed that make building applications easier than ever. If you manage to stay away from pointless discussions about the best state management system, React provides you with a straightforward onboarding experience and all the tools you need to move fast and build great products.