TL;DR
In this article we’ll look at 5 libraries that can positively impact your React development experience by addressing some of the most common pain points in React development such as data fetching, styling, accessibility and state management.
Introduction
Mastering the fundamentals of React is important. And the truth is, you can get quite far without a ton of additional libraries. But there are a few foundational tools that can take your React development experience to the next level. These libraries address the most common pain points in React development such as data fetching, styling, accessibility and state management, and they do so in a minimal and non-intrusive way. This enables gradual adoption across your code base.
We’ve put together a list of five such libraries that we think you should be aware of.
Why This is Important
Sharing devtools and impacting developer experience is important. When developers have access to the right tools and resources, they can spend more time building and creating, and less time with distractions, overhead and frustrations.
That’s why we’re always on the lookout for useful projects to share, and it’s also why we’ve recent;y launched an open source tool of our own called “Preevy” https://github.com/livecycle/preevy.
Developers use Preevy to easily create shareable preview environments and better collaborate with others on the latest changes.
Can you check out Preevy and leave us a star? It really helps us to continue contributing useful tools and content to the open source community.
Thank you ❤️❤️❤️!
And now, with our introductions out of the way, let’s jump in and see what these tools are all about.
TanStack React Query
https://github.com/TanStack/query
Put simply, React Query makes fetching data in React a way better experience. But it’s not a data fetching library per se. Instead, it is a state management library that deals with asynchronous server state. You provided it with an asynchronous function that then fetches data. The useQuery
hook then provides you with a bunch of useful utilities to handle the async function:
- Loading flag
- Result caching
- Invalidation and re-fetching of results
This doesn’t sound like that much. But the influence it has on big code bases cannot be understated. Typically code bases have a lot of logic to share fetch results globally, refresh those when the data changes, triggers to fetch data, and so much more. Most of this is simply no longer needed when using React Query. Caching means you can call the useQuery
hook all over your application and data gets shared between all occurrences.
Zustand
https://github.com/pmndrs/zustand
Every React developer knows the pain involved in sharing state across your application. When first encountered with the problem, you inevitably end up “prop drilling” the data across the component tree. Needless to say, that doesn’t make for clean code and isn’t sustainable in the long run.
Thankfully, React came up with Context providers to solve this issue. Context is great if all you need to do is pass a few values down your component tree. But it can become cumbersome to use for more complex global stores. Both because developers need to be careful about performance implications and some developers aren’t a big fan of its API.
If you want to set up from Context, Zustand is your best bet. It offers an extremely simple API that lets you create a store with values and functions. Then, you can access that store from anywhere in your application to read and write values. Reactivity included! If you want to store nested object data in your store, consider using Immer alongside Zustand to easily change nested state.
Framer Motion
https://github.com/framer/motion
Animations are one of the best ways to give your React application a modern and polished feel. But this isn’t easy. Using CSS animations is tricky and can result in a lot of code. In contrast, Framer Motion offers a powerful but simple API to create custom animations. It’s natively integrated into the React Ecosystem with a set of hooks and components.
For example, this code is all that’s required to smoothly animate the transformation from a circle into a square:
import { motion } from "framer-motion"
export const MyComponent = () => (
<motion.div
animate={{
scale: [1, 2, 2, 1, 1],
rotate: [0, 0, 270, 270, 0],e borderRadius: ["20%", "20%", "50%", "50%", "20%"],
}}
/>
)
Each value in the array represents a keyframe for the corresponding property. The animation then loops through that. Of course, you can do much more than simply defining keyframes with Framer Motion. You can also animate changes in Layout, handle gestures or animate based on scrolling.
Class Variance Authority (CVA)
https://github.com/joe-bell/cva
TailwindCSS has quickly risen to become the prime way of styling React Applications. But building reusable UI elements with it can be a challenge. Say you create your own custom styled button using Tailwind. Since you want to reuse it throughout your app you create a Component. But now you want multiple variants of that component. A primary style and a secondary style. So now you need to piece your Tailwind classes together according to the prop value. Now you also want different colors and various sizes for your button. So add some props and even more conditional logic to figure out the correct combination of Tailwind classes. This can get frustrating quite quickly.
Enter CVA, short for Class Variance Authority. It’s a simple library that takes the pain away from building composable React components with Tailwind class names Take this example from their docs:
import React from "react";\
import { cva, type VariantProps } from "class-variance-authority";****\
const button = cva("button", {\
variants: {\
intent: {\
primary: [\
"bg-blue-500",\
"text-white",\
"border-transparent",\
"hover:bg-blue-600",\
],\
secondary: [\
"bg-white",\
"text-gray-800",\
"border-gray-400",\
"hover:bg-gray-100",\
],\
},\
size: {\
small: \["text-sm", "py-1", "px-2"],\
medium: \["text-base", "py-2", "px-4"],\
},\
},\
compoundVariants: \[{ intent: "primary", size: "medium", class: "uppercase" }],\
defaultVariants: {\
intent: "primary",\
size: "medium",\
},\
});
export interface ButtonProps\
extends React.ButtonHTMLAttributes<HTMLButtonElement>,\
VariantProps<typeof button> {}****\
export const Button: React.FC<ButtonProps> = ({\
className,\
intent,\
size,\
...props\
}) => <button className={button({ intent, size, className })} {...props}
/>;
We declaratively describe the button styles for each parameter value. CVA then does the work of figuring out the correct combination of styles. We can even specify the default variants to make certain properties optional.
Radix UI
https://github.com/radix-ui/primitives
If you like to build fully custom styled interfaces but don’t want to deal with the intricacies of developing high-fidelity accessible UI components from scratch, Radix UI is for you. The library ships with various commonly used UI components. Such as Dialogs, Checkboxes, and Dropdowns. But with a twist.
While the components contain all the logic and interactivity, they have zero styling. This means you have full control over styling the components yourself. This enables you to build a truly custom UI system that doesn’t look like every other website. While having full control over styling, Radix does all the other work for you. All components are fully accessible - say through keyboard navigation.
If you like the flexibility of Radix but don’t want to style everything from scratch, shadcn/ui is something you should check out. It’s a fully modular component library built on top of Radix and Tailwind. Instead of installing an NPM package, you can copy the code directly into your project and modify it to your liking.
Wrapping Up
The libraries discussed in this article can help you bring your React applications to the next level. Adopting them will help your app have a better experience for both users and developers. You can adopt all of them gradually in your project instead of with one big change. And they’re very straightforward to get started with. So there’s no need to spend hours on end studying the documentation before you can begin coding.
Hope you found this helpful!