Top 8 Recommended Libraries for a React + Typescript Project

There are thousands of libraries you can add to your project. Actually... hundreds of thousands.

NPM reports having over 650,000 public packages available!

On one hand, this is great because you have so much selection... you can do whatever you want.

On the other hand, this can feel paralyzing because determining which libraries you need can be a long, painful process. Choose poorly and soon you'll face the horrible decision of fighting against a library you hate versus spending time-you-don't-have to rewrite your code.

This paralysis is compounded by the difficulty of discovering what libraries even exist. There could be a library that solves your exact needs but how would you know? Or you found a library that you think will work for you but how do you know if there isn't a better one out there?

You could read blogs and forums; follow high-profile developers on social media; read through open source projects to see what other developers are using.

But it all takes a long time and you're starting a project and need to know NOW.

To shortcut the process, I'm going to show you the libraries I use and recommend for React + TypeScript projects.

The Core Libraries I Keep Using Again and Again

I've worked on React + TypeScript projects for years now and I've come to rely on a core set of libraries.

Other libraries come and go, depending on the specific needs of the project. But these are the ones that are always there.

Redux

React-Redux

Reselect

Recompose

Redux-Saga

Redux-Thunk

React-Router

React-Router-Redux

I'm going to break them down one-by-one for you.

Redux

I think Redux is best described as a paradigm for managing application state in a simple, predictable way.

And it truly is simple...

But it's also notoriously confusing for beginners because it requires a big mental leap from the way data is traditionally managed.

In Redux, the state object is immutable and the only way to change state is by dispatching actions to a reducer. An action is typically a plain JavaScript object that has a type property and often some sort of payload. Actions are created by action creators (for lack of better terminology).

Here's a sample action creator that sets a counter

export const setCount = ( n : number ) => ({ type : 'SET_COUNT' , payload : n , });

Notice that the action creator doesn't actually change anything itself. It simply creates an action object.

const action = setCount ( 10 );

A Reducer is a function that takes a state object and an action... and it returns a new state. For instance, this reducer handles the SET_COUNT action.

export const exampleReducer = ( state : State , action : Action ) => { switch ( action . type ) { case 'SET_COUNT' : return { ... state , count : action . payload }; default : return state ; } };

Here's what it looks like when we pass an action through the reducer. But note that we wouldn't write code like this in a real app. This is just to demonstrate the pieces working together. In a real app, we'd dispatch the action to the store and the Redux middleware would pass the action to our reducers.

const state = { count : 0 }; const action = setCount ( 10 ); const newState = exampleReducer ( oldState , action );

React is often paired with Redux. Why? Because React renders UI based on state and it performs really well with immutable state; and Redux is all about managing immutable state.

It's like they were made for eachother!

To install Redux and its type definitions,

npm i -- save redux @ types / redux

React-Redux

React components and Redux state go together hand in hand. But, they need to be connected so that you can access and update state from the components.

React-redux is the library that connects the two. Essentially, it is a higher-order component that wraps another component to provide redux state via React context.

Don't worry if you're unfamiliar with higher-order components or React context. You can get started now and learn those concepts later on.

You may have encountered functions called mapStateToProps and mapDispatchToProps. These are functions used to describe how to bind Redux state and actions to a React component.

mapStateToProps is for reading Redux data. This example takes the count from state and puts it in a prop named count, which will get passed into a React component.

type StateProps = { count : number ; }; const mapStateToProps = ( state : State ): StateProps => ({ count : state . count , });

and mapDispatchToProps is for sending changes back to Redux. This example provides a method for the UI component to reset the count.

type DispProps = { resetCount : () => void ; }; const mapDispatchToProps = ( dispatch : Function ): DispProps => ({ resetCount : () => dispatch ( setCount ( 0 )), });

The two mapping functions are activated by connecting them to a component. We use the connect() method for this.

So take this component called RawCounter. It's a 'dumb' UI component that takes the count and a resetCount function...

type Props = StateProps & DispProps & OwnProps ; const _ Counter = ({ count , resetCount }: Props ) => < div > < span > The count is { count } </ span > < button onClick ={ resetCount }> reset </ button > </ div > ;

...and then we can connect it to Redux using the connect() method:

const enhance = connect < StateProps , DispProps , OwnProps >( mapStateToProps , mapDispatchToProps , ); export const Counter = enhance (_ Counter );

Install react-redux and its typings with this command:

npm i -- save react - redux @ types / react - redux

Reselect

If Redux is the missing piece of React, then selectors are the missing piece of Redux.

The Redux primitives, which consist of actions and reducers, are only concerned with updating state.

Selectors, on the other hand, are for retrieving data from state... but in a way that decouples UI components from knowledge of the state structure.

A simple selector is just a function that returns some specific piece of state.

For example, consider the mapStateFromProps code from the previous section:

const mapStateToProps = ( state : State ): StateProps => ({ count : state . count , });

We could decouple the state structure from our UI with a getCount selector

export const getCount = ( state : State ) => state . count ;

And then use the selector in our mapping function

const mapStateToProps = ( state : State ): StateProps => ({ count : getCount ( state ), });

Selectors don't have to be straight look-ups of data, though.

You can compose them together and use them to perform calculations to return derived data.

But often you own want to perform a calculation once... because perhaps it's an expensive calculation and you don't want to do it every time the UI is rendered... or more likely you're working with arrays or objects and you want to avoid unnecessary UI re-renders.

This is exactly what Reselect is for.

Reselect supercharges your selectors by adding memoization, which is a fancy word that just means it will compute the result and cache it.

For example, here is a selector that composes three other selectors to return a sorted list of activities performed by a user. The result is only computed when one the three input selectors changes.

const noActivities : Activity [] = []; export const getUserActivities = createSelector ( getUser , getActivities , getActivitiesSortField , ( user , activities , sortField ) => { if (! user ) return noActivities ; if (! activities ) return noActivities ; const userActivities = activities [ user . id ]; if (! userActivities ) return noActivities ; return [ ... userActivities ]. sort (( a , b ) => { if ( a [ sortField ] < b [ sortField ]) return -1 ; if ( a [ sortField ] > b [ sortField ]) return 1 ; return 0 ; }); } );

Install Reselect and its type declarations with this command:

npm i -- save reselect @ types / reselect

Recompose

Recompose is a collection of higher-order components that make working with stateless-functional components easier in React.

A higher-order component is a function that takes a component and wraps it with new behaviour.

And a stateless-functional component is a function that renders DOM elements without all the other complexity that is involved with React components (like state and lifecycle methods).

These are advanced concepts, however. So feel free to skip recompose if you're just starting to learn React.

npm i -- save recompose @ types / recompose

Redux-Thunk

Redux-thunk makes it easy to perform series of asynchronous tasks.

For instance, let's say you need to request something from the server and you want to store the result in your Redux store. At the same time, it would be nice to show a loading indicator while your users wait. And you probably also want to be able to handle errors gracefully.

Logic like this can be easily written as a thunk like this:

import { UserId } from './types' ; import { api } from './api' ; import { loadUserRequest , loadUserSuccess , loadUserFailure } from './userActions' ; export const loadUser = ( userId : UserId ) => async ( dispatch : Function ) => { dispatch ( loadUserRequest ( userId )); try { const user = await api . getUser ( userId ); dispatch ( loadUserSuccess ( user )); } catch ( err ) { dispatch ( loadUserFailure ( err )); } };

We can call loadUser with an ID and it returns us the thunk, which is to say, an action that is actually a function rather than a plain JavaScript object. The redux-thunk middleware will call the function for us.

You can install redux-thunk and its type declarations with this command:

npm i -- save redux - thunk @ types / redux - thunk

Redux-thunk however is not the only middleware library for performing asynchronous operations. There are many. The other highly popular one is called Redux-Saga.

Redux-Saga

Redux-Saga is a general-purpose middleware library for Redux that allows you to easily write complex asynchronous workflows.

You can use it to build things like user onboarding sequences, multi-step data entry forms, and do aspect-oriented tasks like analytics, logging, call-retry logic, etc. and much much more.

Really... Anything you can dream up.

Here's an example of two sagas that work together to perform long polling of the server once a user is logged in. The loginWatcher waits for a user to login and then begins the polling saga. It also terminates the poll if the user logs out. And pollSaga simply calls the server and stores the response repeatedly forever... until it's cancelled.

import { api } from './api' ; import { pollResponse } from './actions' ; export function* loginWatcher () { while ( true ) { yield take ( 'LOGIN_SUCCESS' ); const pollTask = yield fork ( pollSaga ); yield take ( 'LOGOUT' ); yield cancel ( pollTask ); } } export function* pollSaga () { while ( true ) { const response = yield call ( api . poll ); yield put ( pollResponse ( response )); } }

People often notice the overlap between redux-thunk and redux-saga and ask which is better.

Anything you can write with a thunk can also be written as a saga, but the reverse is not true. The main advantage sagas have over thunks is that they can block execution while waiting for a specific action to be dispatched. Whereas thunks cannot wait on actions at all.

On the other hand, thunks are easier to debug when something goes wrong. And sagas have poor TypeScript support because of the way generator functions work.

There's no harm in using both redux-saga and redux-thunk on a project. But typically you'll use one or the other. If you're new to React then perhaps just stick with redux-thunk for now.

However, if you want to try redux-saga, install it and the type definitions like this:

npm i -- save redux - saga @ types / redux - saga

React-Router

React-router is the library you need for deciding what components to show based on the URL. Any app that has dynamic views inside a page would do well to include react-router. (So basically every Single Page App)

React-router code looks like this. Pay attention to the Switch and Route elements.

import { Switch , Route } from 'react-router' ; import { Header } from './header' ; import { ProfilePage , MessagesPage , 404 Page } from ' ./ pages ; export const Shell = () => < div className = "shell" > < Header /> < div className = "content" > < Switch > < Route path = "profile" component ={ ProfilePage }/> < Route path = "messages" component ={ MessagesPage }/> < Route component ={ 404 Page }/> </ Switch > </ div > </ div > ;

You can install react-router and its type definitions like this:

npm i -- save react - router @ types / react - router

React-Router-Redux

React-router-redux is simply the redux bindings for react-router. It provides action creators that give you the ability to change the URL from code.

You can navigate forward, backward, you can replace the current history state with another, etc.

This example shows how to go back when a button is clicked

import * as React from 'react' ; import { connect } from 'react-redux' ; import { goBack } from 'react-router-redux' ; const _ MyComponent = ({ handleClickBack }: DispProps ) => < div > < button onClick ={ handleClickBack }> Go Back </ button > </ div > ; type DispProps = { handleClickBack : React . MouseEventHandler < HTMLButtonElement >; }; const mapDispatchToProps = ( dispatch : Function ): DispProps => ({ handleClickBack : event => { dispatch ( goBack ()); }, }); export const MyComponent = connect <{}, DispProps , {}>( null , mapDispatchToProps )( _ MyComponent );

The example starts with a stateless functional component. It takes dispatch properties, which consist of an event handler. I've got some type annotations in there. Then a mapDispatchToProps function. Recall that this is standard react-redux infrastructure for binding UI components with functions that update data in Redux. The relevant part is inside the handleClickBack function. Notice that we call goBack() (which creates an action for us) and then we dispatch that action to Redux for processing.

You can install react-router-redux and its type definitions like this:

npm i -- save react - router - redux @ types / react - router - redux

Wrap Up

I've shown you the libraries that I trust and use in my React + TypeScript projects and how you can add them to your projects.

Now that you know what to look for, you can hopefully get past the paralysis and focus on learning and applying the various technologies.