Comparing TypeScript state management solutions
Tags: typescript, webdev
Published: 2024-02-06 15:04:58

**Written by [Rashedul Alam
](https://blog.logrocket.com/author/rashedulalam/)✏️**

State management in frontend development deals with maintaining the state or data knowledge across multiple application components. It’s an essential concept while working with frontend JavaScript and TypeScript frameworks and libraries, especially React, React Native, Vue, and Angular. 

Dealing with state management in TypeScript brings extra benefits, as TypeScript provides type safety. We can define types in each state management singleton and use the type definition with our modern code editors to show errors and write code with the correct syntax and format. 

Although most state management solutions are biased toward React, there are a few for other frontend frameworks, and the number of solutions available continues to rise. In this article, we will compare some commonly used state management solutions in TypeScript and see their basic examples.

## Typical requirements for state management solutions

State management plays a crucial role in building modern web applications. It ensures that data is well-organized, updated, and efficiently shared across different parts of the application. 

When developers explore various TypeScript state management options, they often seek features that address common challenges while keeping things flexible. Let’s discuss some of the challenges that come with state management and some essential features you might need from the solution you choose.

### Dealing with state management challenges

As your projects get bigger, dealing with state complexity can be challenging. Finding a solution that simplifies things without giving up flexibility is crucial. 

Maintaining a uniform structure across different application parts is critical for predictable behavior and a user-friendly experience. When handling updates simultaneously and avoiding conflicts, especially in applications with asynchronous tasks, caution and attention to detail are key. 

With TypeScript becoming more popular, developers emphasize state management to catch errors early on during compilation rather than waiting for them to appear at runtime.

### Essential features in state management solutions

Ideally, your state management solution should make modifying and updating the application state easy, simplifying code debugging and understanding. By centralizing the application state, you gain better control and monitoring, allowing you to track changes better and reducing the risk of inconsistencies. 

Embracing a reactive paradigm allows components to automatically update with changes in the underlying state, minimizing the need for manual adjustments to keep the user interface in sync. 

Additionally, it's essential for state management solutions to scale smoothly with the application's complexity and size, ensuring optimal performance as the project grows. 

Finally, developer tools such as time-travel debugging and state inspection add valuable insights into the application's state at different points in time, enhancing the development process.

### Type safety and scalability in TypeScript applications

For TypeScript applications, type safety is of the utmost importance. A state management solution that seamlessly integrates with TypeScript ensures that developers fix type-related errors early in the development process, enhancing code quality and minimizing runtime issues. 

As applications scale, the value of TypeScript's type safety becomes more apparent. A state management solution that scales well with TypeScript promotes efficient collaboration among developers and facilitates the maintenance of large codebases.

## Some common TypeScript state management libraries

In the next few sections, we’ll highlight the following TypeScript state management libraries:

*   Redux Toolkit and React Redux
*   MobX
*   NgRx
*   Pinia
*   Recoil
*   React Query
*   Jotai

We’ll go over which frameworks these state management solutions are for and some examples of how to install and use them.

### Redux Toolkit and React Redux

We can use [React Redux and Redux Toolkit](https://blog.logrocket.com/understanding-redux-tutorial-examples/) in our React and React Native projects. The React Redux library allows us to seamlessly integrate the Redux state management tool with React apps. Meanwhile, Redux Toolkit further simplifies the configuration and usage of Redux in our projects. 

Redux has many features and benefits, especially when [using TypeScript with React](https://blog.logrocket.com/how-to-use-typescript-react-tutorial-examples/). Some of these include:

*   Helps you build applications that are reliable across various environments, whether it's on the web, server-side, or in mobile apps
*   Provides test-friendly development experience
*   Enhances development experience with features like live code editing and a debugger that lets you travel back and forth in time to inspect your application's state
*   Redux Toolkit provides an easy and simple API interface for using Redux in your application

To install it, simply run the command below:

```bash
npm install @reduxjs/toolkit react-redux
```

Let’s see some examples of how to use Redux Toolkit and React Redux.

#### Create a Redux store

Let’s create a file named `src/lib/store.ts` and copy in the following code:

```typescript
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';

const rootReducer = combineReducers({
  counter: counterReducer,
});

export default configureStore({
  reducer: rootReducer,
});

export type IRootReducer = ReturnType<typeof rootReducer>;
```

This creates a Redux store and automatically configures [the Redux DevTools extension](https://blog.logrocket.com/redux-devtools-tips-tricks-for-faster-debugging/) so we can inspect the store while developing. We will implement the `counterReducer` later in this tutorial. 

Once the store is created, we can make it available to our Redux Toolkit components by putting a React Redux `<Provider>` around our application in `src/index.tsx`. Let’s import the Redux store we just created, add the `<Provider>` in the `<App>` component, and pass the store as a prop:

```typescript
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';

import { App } from './App';
import store from './lib/store';

const root = createRoot(document.getElementById('app'));

root.render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>
);
```

#### Create a Redux state slice

Let’s add a new file named `counterSlice.ts` in the `src/lib/features/counter/` directory. In this file, let’s bring in the `createSlice` API from the Redux Toolkit. 

To make a slice, we should give it a name, an initial state, and one or more reducers to determine how the state can change. Once the slice is created, we can export the Redux action creators it generates and the entire slice's reducer function. 

With Redux Toolkit's `createSlice` and `createReducer`, we can write our state updates like we are making changes directly, even though Redux demands immutable updates:

```typescript
import { createSlice } from '@reduxjs/toolkit';

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
```

The code above sets up a simple counter in a React application and defines a Redux state slice to manage the counter’s state.

#### Use Redux state and actions in React components

Now, we can use the React Redux hooks to let React components interact with the Redux store. We can read data from the store using `useSelector` and dispatch actions using `useDispatch`. 

Let’s modify our `App.tsx` component like below to show the counter and increase and decrease its value:

```typescript
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { decrement, increment } from './lib/features/counter/counterSlice';
import { IRootReducer } from './lib/store';

export function App() {
  const count = useSelector<IRootReducer, number>(
    (state) => state.counter.value
  );
  const dispatch = useDispatch();

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  );
}
```

The `Counter` component we set up here interacts with the `counterSlice` component we created previously to access the current state of the `counter` slice and display and modify its values in the component. 

You can check out this example in [the StackBlitz repo](https://stackblitz.com/edit/stackblitz-starters-cd4p3c?file=src%2FApp.tsx). 

Although we focused on the React app in this article, we can use Redux in almost all JavaScript and TypeScript view libraries. For more information on getting started on Redux state management, you can visit the [Redux Getting Started page](https://redux.js.org/introduction/getting-started).

### MobX with MobX React Lite

MobX is a well-tested library for managing states in a simple and scalable way using functional reactive programming. The idea is to keep the code minimal and straightforward. 

If we want to update a field in a record, we can use a regular JavaScript assignment, and MobX will update everything else automatically. It also ensures efficient rendering and tracking of changes to our data at runtime to only update what's necessary, saving us from manual optimizations like memorization. 

What's remarkable about MobX is its flexibility. We can handle our application state independently of any UI framework. This makes the code more modular, portable, and easy to test. So, in a nutshell, MobX helps us manage state effortlessly and efficiently in your applications. 

MobX is versatile — it works in various environments, like browsers and Node.js projects that support ES5. 

Regarding [React bindings for MobX](https://blog.logrocket.com/introduction-to-mobx-with-react/), you can choose between `mobx-react-lite` for functional components or `mobx-react` for functional and class-based components. To install MobX, simply add the correct binding to your Yarn or npm command based on your needs:

```bash
npm install --save mobx
npm install --save mobx-react-lite # or, npm install --save mobx-react (based on your preference)
```

#### Example usage of MobX for state management in React

Let’s work with another React app, this time to create a timer instead of a counter. Paste this code inside a React component:

```typescript
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';

function createTimer() {
  return makeAutoObservable({
    secondsPassed: 0,
    increase() {
      this.secondsPassed += 1;
    },
    reset() {
      this.secondsPassed = 0;
    },
  });
}

const myTimer = createTimer();

const TimerView = observer(
  ({
    timer,
  }: {
    timer: { secondsPassed: number; increase(): void; reset(): void };
  }) => (
    <button onClick={() => timer.reset()}>
      Seconds passed: {timer.secondsPassed}
    </button>
  )
);

export const App = () => <TimerView timer={myTimer} />;

setInterval(() => {
  myTimer.increase();
}, 1000);
```

You can view and interact with the example in [this StackBlitz repo](https://stackblitz.com/edit/stackblitz-starters-uxap9q?file=src%2FApp.tsx). 

When we wrap the `TimerView` React component with the `observer`, it understands that it needs to update whenever the `timer.secondsPassed` changes, even if we don't explicitly mention it. Thanks to the reactivity system in MobX, our component will automatically re-render whenever that specific field is updated. 

So, whenever we click on the button or use `setInterval`, it triggers an action — `Timer.increase` or `myTimer.reset` — updating the observable state, or `myTimer.secondsPassed`. This update then propagates smoothly to all the computations and side effects like `TimerView` that rely on those changes: ![Diagram Showing The Mobx State Management Solution's High Level Design Pattern](https://blog.logrocket.com/wp-content/uploads/2024/02/Diagram-high-level-design-pattern-MobX.png) 

Although we focused on the React app in this article, MobX is supported by some other view libraries as well. For more details, you can [visit the MobX documentation](https://mobx.js.org/README.html).

### NgRx

[NgRx is a store management solution for Angular](https://blog.logrocket.com/angular-state-management-made-simple-with-ngrx/). It’s an RxJS-based global state management solution inspired by Redux that helps you handle the application's state in a way that makes it easy to maintain and understand. Using a single state and actions lets you clearly express how your application's state changes over time. 

Some notable features of the NgRx Store include:

*   Follows the principle of a single immutable state tree, making it the single source of truth for the entire application
*   Seamlessly integrates with Angular, aligning with Angular's architecture and providing a consistent way to manage state in Angular applications
*   Utilizes TypeScript for strong typing, improving development tooling, and reducing the likelihood of runtime errors
*   Designed to be testable, making it easier to write unit tests for actions, reducers, effects, and selectors
*   Leverages Angular's dependency injection system, making it easy to inject the store into components, services, and other parts of the application

To install the NgRx Store in your project, you need to enter the following command in your terminal:

```shell
ng add @ngrx/store@latest
```

#### Example usage of NgRx for state management

Let’s create a new Angular project using Angular CLI. Please [check the Angular documentation](https://angular.io/cli) for help if you are new to Angular or Angular CLI. After initializing a new Angular project and installing our `@ngrx/store` library, we can proceed to the next stage. 

Note that we’re implementing our simple NgRx state management example using [Angular 17, which differs significantly from previous versions](https://blog.logrocket.com/exploring-angular-evolution/). Also, this tutorial differs slightly from the original documentation. If you are using an older Angular version, you should [follow the appropriate documentation](https://angular.io/guide/releases). 

Let’s work on another counting example. Let’s create a new file, `counter.actions.ts`, in the `src/app/` directory and paste the following code:

```typescript
import { createAction } from '@ngrx/store';

export const increment = createAction('[Counter Component] Increment');
export const decrement = createAction('[Counter Component] Decrement');
export const reset = createAction('[Counter Component] Reset');
```

The above code describes unique events that are dispatched from components and services. Next, we’ll define a reducer function — `src/app/counter.reducer.ts` — to handle changes in the counter value based on the provided actions:

```typescript
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';

export const initialState = 0;

export const counterReducer = createReducer(
  initialState,
  on(increment, (state) => state + 1),
  on(decrement, (state) => state - 1),
  on(reset, (state) => 0)
);
```

Now, let’s add the `counterReducer` in the `app.config.ts`:

```typescript
**import { ApplicationConfig } from '@angular/core';

import { provideStore } from '@ngrx/store';
import { counterReducer } from './counter.reducer';

export const appConfig: ApplicationConfig = {
  providers: [provideStore({
    count: counterReducer
  })],
};
```

Let’s create a new component called `my-counter` using the following command:

```shell
ng g c my-counter
```

This command should create all the files we need, including the `my-counter.component.ts` file and the `my-counter.component.html` file. Copy the following code into the `my-counter.component.ts` file:

```typescript
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { increment, decrement, reset } from '../counter.actions';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-my-counter',
  templateUrl: './my-counter.component.html',
  standalone: true,
  imports: [CommonModule],
})
export class MyCounterComponent {
  count$: Observable<number>;
  constructor(private store: Store<{ count: number }>) {
    this.count$ = store.select('count');
  }
  increment() {
    this.store.dispatch(increment());
  }
  decrement() {
    this.store.dispatch(decrement());
  }
  reset() {
    this.store.dispatch(reset());
  }
}
```

Next, let’s copy the following code in the `my-counter.component.html` file:

```html
<button (click)="increment()">Increment</button>
<div>Current Count: {{ count$ | async }}</div>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset Counter</button>
```

Now, let’s add this new component in `AppComponent`. Paste the following code in your `app.component.ts` file:

```typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { MyCounterComponent } from './my-counter/my-counter.component';
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet, MyCounterComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = 'angular-project';
}
```

Finally, declare it in the app template:

```html
<h1>Hello World!</h1>
<app-my-counter></app-my-counter>
```

The code above sets up a simple counter in an Angular application using the NgRx Store for state management. 

For more details, you can [go through the NgRx documentation](https://ngrx.io/docs).

### Pinia

[Pinia is the official state management library for Vue](https://blog.logrocket.com/complex-vue-3-state-management-pinia/). It enables seamless state-sharing among components and pages. While the Composition API allows us to share global states easily in SPAs, it might expose our app to security vulnerabilities if it's rendered on the server side, making Pinia crucial. 

Even in smaller applications, Pinia brings several advantages, including:

*   Devtools support
*   A timeline for action and mutation tracking
*   Stores conveniently appearing where they are used in components
*   Time travel for debugging
*   Hot module replacement for modifying stores without reloading the page
*   The ability to keep the existing state during development

Pinia also offers plugins to extend its features, robust TypeScript support, and compatibility with server-side rendering. 

To use Pinia, you must first install it using your [favorite package manager](https://blog.logrocket.com/javascript-package-managers-compared/):

```bash
yarn add pinia
# or with npm
npm install pinia
```

Then, create a Pinia instance — the root store — and pass it to the app as a plugin:

```typescript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')
```

If you are using Vue 2, you also need to install a plugin and inject the created Pinia at the root of the app:

```typescript
import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin)
const pinia = createPinia()

new Vue({
  el: '#app',
  pinia,
})
```

This will also add support for Devtools. While Vue 3 currently lacks certain features like time traveling and editing due to Vue Devtools limitations, the overall developer experience is significantly enhanced with many Devtools features.

#### Basic example using Pinia for state management

Let’s keep working on the counting example. Create a file called `src/stores/counter.ts` and paste in the following code:

```typescript
// src/stores/counter.ts
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++
    },
  },
})
```

After that, we can use it in a component as shown below:

```typescript
<script setup>
import { useCounterStore } from './stores/counter';

const counter = useCounterStore();
</script>

<template>
  <div>Current Count: {{ counter.count }}</div>
  <button @click="counter.increment()">Increment</button>
</template>
```

As we can see, we are getting the state info from `useCounterStore()`. From that, we can show the state value through the component and also modify the state values using the store actions. 

You can [check the complete StackBlitz repo](https://stackblitz.com/edit/vitejs-vite-s16shz?file=src%2FApp.vue) to see and interact with the demo. For more details, please [visit the Pinia documentation](https://pinia.vuejs.org/introduction.html).

### Recoil

[Recoil is another state management library for React](https://blog.logrocket.com/simple-state-management-react-recoil/), built by Meta. It lets you create a data-flow graph where you flow your shared states (atoms) through functions (selectors) and eventually reach your React components. 

Some notable Recoil features include:

*   Introduces the concept of an "atom," which represents a piece of state. Atoms can be easily created and accessed, providing a simple and efficient way to manage the state
*   Provides selectors, which allow us to derive computed values from atoms. These can be used to create a derived state or perform calculations based on the current state. Atoms are like state units that your components can subscribe to, and selectors can transform this state synchronously or asynchronously
*   Designed to work well with TypeScript, providing type safety for your state. This includes defining the types of your atoms and selectors and enhancing the development experience
*   Supports asynchronous operations, allowing you to handle async data and side effects using asynchronous selectors or effects

Installing Recoil is pretty straightforward, just like the other packages. We can use any of our favorite package managers to install the state management library in our React app:

```bash
npm install recoil
# or using yarn
yarn add recoil
# or using bower
bower install --save recoil
```

#### Understanding `RecoilRoot`

Components with states that are managed by Recoil need the `RecoilRoot` component to appear somewhere in the parent tree. The `RecoilRoot` component is essential for creating the context for Recoil state management. Without adding this, the Recoil state management will not work. 

An excellent place to put this component is in your root component, like so:

```typescript
import {
  atom,
  RecoilRoot,
  selector,
  useRecoilState,
  useRecoilValue,
} from 'recoil';

export const App = () => {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
};
```

In this example, we are building a character-counter example. We will take the text input and show the character length as output. We'll implement the `CharacterCounter` component in the following section.

#### The purpose of an atom in Recoil

An atom represents a piece of state. Atoms can be read and written from any component. Components that read the value of an atom are implicitly subscribed to that atom, so any atom updates will result in a re-render of all components subscribed to that atom:

```typescript
const textState = atom({
  key: 'textState',
  default: '',
});
```

Usually in React, we use [the `useState` Hook](https://blog.logrocket.com/guide-usestate-react/) to read from and write to the local state of a component. But with Recoil, to read from and write to an atom, we need to use `useRecoilState()` like this:

```typescript
const [text, setText] = useRecoilState(textState); 
```

We can read from the `text` variable and write using the `setText` function. So, components that need to read from and write to an atom should use `useRecoilState()` as shown below:

```typescript
const TextInput = () => {
  const [text, setText] = useRecoilState(textState);

  const onChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
};

const CharacterCounter = () => {
  return (
    <div>
      <TextInput />
      <CharacterCount />
    </div>
  );
};
```

#### Using selectors in Recoil

A selector represents a piece of derived state, which is a transformation of a state that gets computed based on other states and can change when those states change. We can think of the derived state as the output of passing a state to a pure function that modifies the given state in some way:

```typescript
const charCountState = selector({
  key: 'charCountState',
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});
```

The code above determines the length of the `textState` value. We can use the `useRecoilValue()` Hook to read the value of `charCountState` and show the character count of the input value in our component:

```typescript
const CharacterCount = () => {
  const count = useRecoilValue(charCountState);
  return <>Character Count: {count}</>;
};
```

You can find the full code for this example in [this StackBlitz repo](https://stackblitz.com/edit/stackblitz-starters-e7hsnp?file=src%2FApp.tsx). For more details, you can [visit the Recoil documentation](https://recoiljs.org/docs/introduction/getting-started).

### React Query (TanStack Query)

[React Query — officially called TanStack Query](https://blog.logrocket.com/react-query-state-management-react-native/) — is an asynchronous state management library for React and React Native. It can handle tasks like data fetching, caching, synchronizing, and updating server state in your React apps. 

While other state management solutions provide a way to view and update states across the application, React Query provides solutions to manage the data that we get from the API. 

We can install React Query by running the following command:

```bash
npm i @tanstack/react-query
# or
yarn add @tanstack/react-query
```

#### Basic example using React Query for state management

We can fetch the data from an API using the `useQuery` Hook. Similarly, we can request any POST, PUT, PATCH, or DELETE operation using the `useMutation` Hook. Here is a simple example in the React `App.tsx` file, where we will fetch some todo data from a fake API:

```typescript
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';

const queryClient = new QueryClient()

const API_ENDPOINT = `https://jsonplaceholder.typicode.com`;

const Todo = () => {
  const { data } = useQuery({
    queryKey: [`${API_ENDPOINT}/todos`],
    queryFn: () => fetch(`${API_ENDPOINT}/todos`).then((res) => res.json()),
  });
  console.log(data);
  return (
    <div>
      {(data || []).map((item) => (
        <div key={item.id}>
          <h4 style={{ lineHeight: '100%', marginBottom: 0 }}>{item.title}</h4>
          <p>{item.completed ? 'Done' : 'Not Done Yet'}</p>
        </div>
      ))}
    </div>
  );
};

export const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <Todo />
    </QueryClientProvider>
  );
};
```

The complete code is available in [this StackBlitz repo](https://stackblitz.com/edit/stackblitz-starters-hsczjg?file=src%2FApp.tsx). To learn more, you can go through the [React Query documentation](https://tanstack.com/query/v3/docs/react/overview).

### Jotai

Jotai is another state management library for React. Like Recoil, it takes an atomic approach to global React state management. 

We can create a state by putting together "atoms," or pieces of state. The renders automatically get smarter based on what these atoms depend on. 

This clever trick solves the problem of unnecessary re-renders in the React context, eliminating the need for memoization. It gives developers a smooth experience, similar to using signals while sticking to a clear and straightforward programming style, making it scalable. 

To install Jotai in your project, simply use one of the commands below:

```bash
# npm
npm i jotai

# yarn
yarn add jotai

# pnpm
pnpm add jotai
```

#### Configuring Jotai for your framework

Adding the optional SWC or Babel plugin is recommended to enable React Fast Refresh support for the best developer experience specific to each framework. Let’s see some popular configuration options. 

If you want to add the SWC plugin to a Next.js project, do the following:

```bash
# npm
npm install --save-dev @swc-jotai/react-refresh

# next.config.js
experimental: {
  swcPlugins: [['@swc-jotai/react-refresh', {}]],
}
```

Meanwhile, you can add the Babel plugin to your Next.js project like so:

```typescript
># .babelrc
{
  "presets": ["next/babel"],
  "plugins": ["jotai/babel/plugin-react-refresh"]
}
```

For Vite, you can add the SWC plugin to a React project with the code below:

```bash
# npm
npm install --save-dev @swc-jotai/react-refresh

# vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';

export default defineConfig({
  plugins: [
    react({
      plugins: [['@swc-jotai/react-refresh', {}]],
    }),
  ],
});
```

For Gatsby, you can add the Babel plugin with the code below:

```bash
# npm
npm install --save-dev babel-preset-gatsby

# .babelrc
{
  "presets": ["babel-preset-gatsby"],
  "plugins": ["jotai/babel/plugin-react-refresh"]
}

# gatsby-config.js
flags: {
  DEV_SSR: false,
}
```

#### Basic example using Jotai for state management

Here is a basic example of [state management in Next.js with Jotai](https://blog.logrocket.com/using-jotai-next-js/), where we will manage our state taken from the input, transform its value to uppercase value, and show it to another component. 

Let’s first add our atoms in `src/lib/atom.ts` file:

```typescript
import { atom } from 'jotai';

export const textAtom = atom('hello');
export const uppercaseAtom = atom((get) => get(textAtom).toUpperCase());
```

Now, let’s add these two components in our `src/components` folder: `Input` component and `Uppercase` component:

```typescript
// src/components/Input.tsx
'use client';

import { textAtom } from '@/lib/atoms';
import { useAtom } from 'jotai';

export const Input = () => {
  const [text, setText] = useAtom(textAtom);
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) =>
    setText(e.target.value);

  return <input className="border" value={text} onChange={handleChange} />;
};

// src/components/Uppercase.tsx
'use client';

import { uppercaseAtom } from '@/lib/atoms';
import { useAtom } from 'jotai';

export const Uppercase = () => {
  const [uppercase] = useAtom(uppercaseAtom);
  return <div>Uppercase: {uppercase}</div>;
};
```

Now, let’s add these two components to our homepage via the `src/app/page.tsx` component file:

```typescript
import { Input } from '@/components/Input';
import { Uppercase } from '@/components/Uppercase';

const Home = () => {
  return (
    <>
      <Input />
      <Uppercase />
    </>
  );
};

export default Home;
```

The complete code is available in [this StackBlitz repo](https://stackblitz.com/edit/stackblitz-starters-h5czkq?file=app%2Fpage.tsx). For more information, you can go through [the Jotai documentation](https://jotai.org/docs/introduction).

## Considerations for choosing a state management solution

While choosing state management libraries for your TypeScript projects, you should consider the following:

1.  **Performance considerations**: One primary goal while building and developing applications is to enhance UX and performance. Try to choose your state management solution depending on your specific project requirements to improve performance and flexibility
2.  **DX and ease of use**: You might be comfortable with a particular set of state management libraries or more familiar with some specific frameworks. This is important to keep in mind while choosing a state management solution, as most are primarily biased toward a particular set of frameworks or libraries. It helps to start with the state management library you’re most comfortable with, especially if it is easy for other developers on your team to learn and use
3.  **Community support and documentation:** Choosing a state management library with the most community support and rich documentation is ideal. Without those resources, you might run into some problems and be unable to find any solutions, which can especially cause trouble as the project grows bigger

With that in mind, here is a comparison table of the solutions we reviewed in this article to help you make your choice:

<table>

<thead>

<tr>

<th>Tool</th>

<th>Library/framework support</th>

<th>Popularity</th>

<th>Documentation</th>

<th>Community support</th>

<th>Ease of use/DX</th>

</tr>

</thead>

<tbody>

<tr>

<td>Redux</td>

<td>Almost all JS / TS view libraries</td>

<td>60.2K stars on GitHub. Popular among almost all professional React developers</td>

<td>Good</td>

<td>Extensive, large, and growing community support</td>

<td>Easy</td>

</tr>

<tr>

<td>MobX</td>

<td>Almost all JS / TS view libraries</td>

<td>27K stars on GitHub. Known by some professional and expert developers; less popular than Redux</td>

<td>Good</td>

<td>Has smaller community support than Redux</td>

<td>Hard</td>

</tr>

<tr>

<td>NgRx</td>

<td>Angular</td>

<td>7.8K stars on GitHub. Popular among almost all professional Angular developers</td>

<td>The Angular version is usually updated frequently, but the documentation can be outdated at times</td>

<td>Extensive, large, and growing community support</td>

<td>Easy</td>

</tr>

<tr>

<td>Pinia</td>

<td>Vue</td>

<td>12K stars on GitHub. Popular among almost all professional Vue developers</td>

<td>Good</td>

<td>Community support is growing</td>

<td>Easy</td>

</tr>

<tr>

<td>Recoil</td>

<td>React</td>

<td>19.3K stars on GitHub. Known by some professional React developers; less popular than Redux</td>

<td>Good</td>

<td>Community support is growing</td>

<td>Easy</td>

</tr>

<tr>

<td>React Query</td>

<td>React</td>

<td>38.2K stars on GitHub. Used by many professional React developers</td>

<td>Good</td>

<td>Extensive, large, and growing community support</td>

<td>Easy</td>

</tr>

<tr>

<td>Jotai</td>

<td>React</td>

<td>16.5K stars on GitHub. Relatively less popular</td>

<td>Good</td>

<td>Community support is growing</td>

<td>Easy</td>

</tr>

</tbody>

</table>

Here are a few resources you can check out for more in-depth comparisons:

*   [Jotai vs. Recoil: What are the differences?](https://blog.logrocket.com/jotai-vs-recoil-what-are-the-differences/)
*   [Comparing Redux vs. Vuex](https://blog.logrocket.com/comparing-redux-vs-vuex/)
*   [Redux vs. MobX: Which performs better?](https://blog.logrocket.com/redux-vs-mobx/)
*   [Pinia vs. Vuex: Which state management library is best for Vue?](https://blog.logrocket.com/pinia-vs-vuex/)

## Conclusion

In this article, we’ve seen many state management libraries that we can use in any of our TypeScript projects. Our choice of state management solution depends on considerations such as the project library, performance considerations, developer experience, community support, and more. 

Generally speaking, state management libraries ease our lives by making our projects scalable, faster, and more robust. State management solutions with TypeScript support improve developer support and minimize bugs and typing errors.

---

##[LogRocket](https://lp.logrocket.com/blg/typescript-signup): Full visibility into your web and mobile apps

[![LogRocket Signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://lp.logrocket.com/blg/typescript-signup)

[LogRocket](https://lp.logrocket.com/blg/typescript-signup) is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

[Try it for free](https://lp.logrocket.com/blg/typescript-signup).