My go-to React libraries for 2021

Julien Hery - May 14 '21 - - Dev Community

I’ve been working on React apps for more than 3 years now and I’ve used tons of libraries to build various applications. The last months have been very rich for the React ecosystem ! We’ve seen many different tools slowly replacing old libraries that hundreds of developers used to build their apps for many years. I started a new project for my client this year and had the opportunity to test out these new tools and choose my new stack for 2021 and beyond.

State Management

I’ve used Redux for many projects, it is (was ?) a great solution to centralize the data of your application. But some things just felt over-complicated and developers have been complaining about this for a long time. For example, learning the Flux pattern can be quite difficult for some people. Then someone tells you that you need to use a middleware to handle your side effects. Great ! Let’s use Redux Thunk. But wait, now someone tells you that there is a better solution : Redux Saga. Nice ! Even more complexity to handle.

But 2020 changed that and new challengers appeared. You might have heard of Recoil, Jotai, Zustand or just React Context API. But the one that made me drop Redux is :

react-query

For those who already know it, you might say that it’s not like Redux. It doesn’t do the same thing. React Query is a data fetching library, it makes fetching, caching, synchronizing and updating server state easy.

All the projects I worked on were using Redux to store data coming from the server. That’s also why middleware like Thunk or Saga appeared.
What if I tell you that there is a library to do that without bothering with actions, action creators, reducers, etc… ? With React Query you’ll be able to easily fetch the data from your server and cache it. Later on you’ll be able to invalidate the data and everything will be re-fetched automatically and stored again. The only part you need to worry about is how you fetch the data. And of course it gives you the isLoading boolean (and tons of other information) you’re tired of handling yourself.

import { QueryClient, QueryClientProvider, useQuery } from 'react-query'

const queryClient = new QueryClient()

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <FirstComponent />
      <SecondComponent />
    </QueryClientProvider>
  )
}

function FirstComponent() {
  // fetch some data
  const { isLoading, error, data } = useQuery('myData', fetchData)

  if (isLoading) return 'Loading...'

  if (error) return 'An error has occurred: ' + error.message

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
    </div>
  )
}


function SecondComponent() {
  const queryClient = useQueryClient()

  const invalidateData = () => {
    // invalidate data, will trigger a refetch in FirstComponent
    queryClient.invalidateQueries('myData')
  }

  return (
    <div>
      <button onClick={invalidateData}>
        Click me to refetch data !
      </button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

If you still need to handle complex local state (i.e. not coming from your server) then I have something for you !

zustand

Zustand is a small and fast state-management solution. I’m not a big fan of context-based libraries like Redux. It always felt like dark magic to me : you don’t really know how it works, but it does the trick 🤷‍♂️. I’m not saying that context-based state management is bad, I’m just saying that the whole Provider/Consumer thing doesn’t feel natural to me even if it’s a React feature.

What I love about Zustand is that it’s just objects ! You create a state from an object, it can contain properties like strings, numbers, boolean or whatever, just as you would do with Redux. But you can also put functions to update your state or even fetch data if you want to handle it yourself (try React Query please. You won’t be disappointed).
Zustand also gives you a hook that you can use in your components to access the data. But the cool thing is : you can also access it outside of a component ! As you may know hooks cannot be used if you’re not in a component, but Zustand gives you the ability to access your state object and every other thing you could do with the hook. This can be very useful for testing, you can just manage your state before rendering your component and you don’t have to wrap everything with a context.

import create from 'zustand'

const useStore = create(set => ({
  bears: [],
  fetchBears: async () => {
    const response = await fetch('/bears')
    set({ bears: await response.json() })
  }
}))

export default function App() {
  const bears = useStore(state => state.bears)

  return <h1>{bears.length} bears around here ...</h1>
}

// You can also use your store outside of components

// Getting non-reactive fresh state
const bears = useStore.getState().bears
// Listening to selected changes, in this case when "bears" changes
const unsub = useStore.subscribe(console.log, state => state.bears)
// Updating state, will trigger listeners
useStore.setState({ bears: [{ name: 'zustand' }] })
// Unsubscribe listeners
unsub()
// Destroying the store (removing all listeners)
useStore.destroy()
Enter fullscreen mode Exit fullscreen mode

I haven’t tried other alternatives like Recoil or Jotai but Zustand is the one that felt the most easy and natural to me by reading its documentation and so far I’m not disappointed.

UI Components

When you don’t have strong styling directives to follow, you’ll probably end up using a component library to have components that already fit well together. Most of the time they also provide you additional features and remove a lot of boilerplate code.

I’ve started React with Reactstrap (Bootstrap for React) and then switched to Material UI. I dropped Reactstrap because Bootstrap was not the cool thing anymore and Material was. But all I felt using Material UI was frustration. To me the components are not always intuitive or flexible enough and I lost too much time searching simple things in the documentation. Material UI was not the right choice for me. So I searched for an alternative and found :

antd

Ant Design is a design system for enterprise-level products. It comes with all the components you need. Every time I wonder “Can I do this with component ?” I can do it.
The documentation is very good and contains a lot of examples. You can feel by reading it that peoples have thought a long time about what features could be useful.

There’s still some things I don’t like about Ant Design. Its style is written using LESS and I’m more of a SASS user (and it’s supported by default by create-react-app). If you want to customize Ant Design theme you need to use LESS or a tool to change LESS variables during your build. To me it feels a little too much to just update color variables.
Another thing to note is that Ant Design is a design system. It is meant to be opinionated. It gives you a set of rules to follow when using components and doesn’t make it very easy when you want to use components outside of these use cases. Overall, it is a pleasure to use compared to Material UI and I can do everything I need very easily without any frustration.

Testing

I was introduced to testing React applications with Enzyme. Back in the day it was probably the best solution. But time has passed and a very strong alternative has appeared :

Capture d’écran 2021-05-10 à 19.15.36

Testing Library has been around for quite some time now and has slowly replaced Enzyme everywhere.
This tool provides simple and complete testing utilities that encourage good testing practices. Unlike Enzyme, it is closer to how a user would interact with your application and gives you more confidence about the way you test your application.

The thing that shocked me the most was the readability of my tests. You can tell exactly what it does by just looking at the code. It may sound stupid, but it wasn’t always the case with Enzyme.

import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import MyComponent from './MyComponent'

test('display "Hello dev.to"', () => {
  const { getByText } = render(<MyComponent />)

  expect(getByText('Hello dev.to')).toBeInTheDocument()
})

test('display "Clicked !" after clicking on button', () => {
  const {getByText, queryByText} = render(<MyComponent />)

  expect(queryByText('Clicked !')).not.toBeInTheDocument()

  fireEvent.click(screen.getByText('Click me'))

  expect(getByText('Clicked !')).toBeInTheDocument()
})
Enter fullscreen mode Exit fullscreen mode

Testing library is not only for React, it has implementations for all frameworks and also provides utilities for tools like Cypress. Nothing much to say about it, you can’t start a React application in 2021 without using Testing Library.

Bonus : Form library

If you do not use a component library (that usually has some components to manage forms) you may need something to handle your forms.

In the past few years, we’ve seen many libraries trying to provide an easy way to handle forms. You may have heard of Redux Form (please, no), Formsy or Formik. I’ve tried them all but the one that convinced me was :

Capture d’écran 2021-05-10 à 19.18.27

React Hook Form is a very simple, hook based library with easy data validation. According to their benchmark it is much faster than other alternatives. By using hooks, React Hook Form feels very natural, it also uses refs (i.e. uncontrolled inputs) to get value from your fields so it’s basically standard javascript.
Formik is another great alternative but feels more complicated to me.

import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, handleSubmit, watch, formState: { errors } } = useForm();
  const onSubmit = data => console.log(data);

  console.log(watch("example")); // watch input value by passing the name of it

  return (
    // "handleSubmit" will validate your inputs before invoking "onSubmit"
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* register your input into the hook by invoking the "register" function */}
      <input defaultValue="test" {...register("example")} />

      {/* include validation with required or other standard HTML validation rules */}
      <input {...register("exampleRequired", { required: true })} />
      {/* errors will return when field validation fails */}
      {errors.exampleRequired && <span>This field is required</span>}

      <input type="submit" />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

What about you ?

Have you recently used new libraries or are you still using the good old ones ? Let me know in the comments ! I'd love to see if I missed some great tools 😄

. . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player