react-tracked vs recoil vs zustand
State Management Libraries for React
react-trackedrecoilzustandSimilar Packages:

State Management Libraries for React

State management libraries are essential tools in React development that help manage and share state across components efficiently. They provide a structured way to handle application state, making it easier to maintain and scale applications. Each library offers unique features and approaches to state management, catering to different use cases and developer preferences. Understanding these libraries can significantly enhance the development process by improving performance, reducing boilerplate code, and simplifying state synchronization across components.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-tracked02,82437.3 kB10a year agoMIT
recoil019,5282.21 MB3223 years agoMIT
zustand057,22095 kB4a month agoMIT

Feature Comparison: react-tracked vs recoil vs zustand

API Design

  • react-tracked:

    react-tracked offers a simple and intuitive API that allows you to create tracked state with minimal boilerplate. It focuses on performance by using a subscription model to ensure that only components that depend on specific state updates will re-render, making it efficient for large applications.

  • recoil:

    Recoil provides a more complex API that includes atoms and selectors, allowing for fine-grained state management. Atoms represent pieces of state, while selectors can compute derived state or perform asynchronous queries. This design enables powerful state management capabilities tailored to complex applications.

  • zustand:

    Zustand features a very minimalistic API that is easy to understand and use. It allows you to create a store with a simple function and provides hooks to access and update state. This simplicity makes it a great choice for developers looking for a straightforward solution.

Performance

  • react-tracked:

    react-tracked is designed with performance in mind, using a subscription model to minimize unnecessary re-renders. This means that only components that directly depend on the changed state will re-render, leading to better performance in larger applications.

  • recoil:

    Recoil's performance is enhanced through its ability to derive state and manage dependencies efficiently. It allows components to subscribe to specific pieces of state, which can lead to optimized rendering and reduced performance overhead in complex applications.

  • zustand:

    Zustand is lightweight and fast, with minimal overhead. It allows for direct state updates without the need for complex state management patterns, making it performant for small to medium-sized applications.

Learning Curve

  • react-tracked:

    react-tracked has a gentle learning curve, making it accessible for developers who are new to state management in React. Its straightforward API allows for quick implementation without overwhelming complexity.

  • recoil:

    Recoil has a moderate learning curve due to its more complex concepts like atoms and selectors. While it offers powerful features, developers may need some time to fully grasp its capabilities and best practices.

  • zustand:

    Zustand is easy to learn and implement, with a simple API that allows developers to get started quickly. Its minimalistic approach means that developers can focus on building features rather than dealing with complex state management patterns.

Use Cases

  • react-tracked:

    react-tracked is best suited for applications where performance is critical, and you want to avoid unnecessary re-renders. It works well in scenarios where state is frequently updated and needs to be efficiently managed across components.

  • recoil:

    Recoil is ideal for complex applications that require a robust state management solution with derived state and asynchronous capabilities. It excels in scenarios where state dependencies are intricate and need to be managed effectively.

  • zustand:

    Zustand is perfect for small to medium-sized applications where simplicity and ease of use are priorities. It is well-suited for projects that need quick state management without the overhead of more complex libraries.

Community and Ecosystem

  • react-tracked:

    react-tracked has a smaller community compared to others, but it is growing. Its simplicity and performance-focused design attract developers looking for efficient state management solutions without the bloat.

  • recoil:

    Recoil is backed by Facebook and has a rapidly growing community. It benefits from strong documentation and a vibrant ecosystem, making it a reliable choice for developers looking for support and resources.

  • zustand:

    Zustand has a supportive community and is gaining popularity due to its simplicity and effectiveness. It is well-documented, making it easy for developers to find help and resources as they implement it in their projects.

How to Choose: react-tracked vs recoil vs zustand

  • react-tracked:

    Choose react-tracked if you need a lightweight solution that focuses on performance and minimal re-renders. It is ideal for applications where you want to optimize rendering behavior and have a simple state management requirement.

  • recoil:

    Select Recoil if you require a more powerful and flexible state management solution with features like derived state and asynchronous queries. It's suitable for complex applications that need fine-grained control over state and want to leverage React's concurrent features.

  • zustand:

    Opt for Zustand if you prefer a minimalistic and straightforward API for state management. It is great for small to medium-sized applications where you want to avoid boilerplate and have a simple, intuitive way to manage global state.

README for react-tracked

logo

React Tracked

CI npm size discord

State usage tracking with Proxies. Optimize re-renders for useState/useReducer, React Redux, Zustand and others.

Documentation site: https://react-tracked.js.org

Introduction

Preventing re-renders is one of performance issues in React. Smaller apps wouldn't usually suffer from such a performance issue, but once apps have a central global state that would be used in many components. The performance issue would become a problem. For example, Redux is usually used for a single global state, and React-Redux provides a selector interface to solve the performance issue. Selectors are useful to structure state accessor, however, using selectors only for performance wouldn't be the best fit. Selectors for performance require understanding object reference equality which is non-trival for beginners and experts would still have difficulties for complex structures.

React Tracked is a library to provide so-called "state usage tracking." It's a technique to track property access of a state object, and only triggers re-renders if the accessed property is changed. Technically, it uses Proxies underneath, and it works not only for the root level of the object but also for deep nested objects.

Prior to v1.6.0, React Tracked is a library to replace React Context use cases for global state. React hook useContext triggers re-renders whenever a small part of state object is changed, and it would cause performance issues pretty easily. React Tracked provides an API that is very similar to useContext-style global state.

Since v1.6.0, it provides another building-block API which is capable to create a "state usage tracking" hooks from any selector interface hooks. It can be used with React-Redux useSelector, and any other libraries that provide useSelector-like hooks.

Install

This package requires some peer dependencies, which you need to install by yourself.

npm add react-tracked react scheduler

Usage

There are two main APIs createContainer and createTrackedSelector. Both take a hook as an input and return a hook (or a container including a hook).

There could be various use cases. Here are some typical ones.

createContainer / useState

Define a useValue custom hook

import { useState } from 'react';

const useValue = () =>
  useState({
    count: 0,
    text: 'hello',
  });

This can be useReducer or any hook that returns a tuple [state, dispatch].

Create a container

import { createContainer } from 'react-tracked';

const { Provider, useTracked } = createContainer(useValue);

useTracked in a component

const Counter = () => {
  const [state, setState] = useTracked();
  const increment = () => {
    setState((prev) => ({
      ...prev,
      count: prev.count + 1,
    }));
  };
  return (
    <div>
      <span>Count: {state.count}</span>
      <button type="button" onClick={increment}>
        +1
      </button>
    </div>
  );
};

The useTracked hook returns a tuple that useValue returns, except that the first is the state wrapped by proxies and the second part is a wrapped function for a reason.

Thanks to proxies, the property access in render is tracked and this component will re-render only if state.count is changed.

Wrap your App with Provider

const App = () => (
  <Provider>
    <Counter />
    <TextBox />
  </Provider>
);

createTrackedSelector / react-redux

Create useTrackedSelector from useSelector

import { useSelector, useDispatch } from 'react-redux';
import { createTrackedSelector } from 'react-tracked';

const useTrackedSelector = createTrackedSelector(useSelector);

useTrackedSelector in a component

const Counter = () => {
  const state = useTrackedSelector();
  const dispatch = useDispatch();
  return (
    <div>
      <span>Count: {state.count}</span>
      <button type="button" onClick={() => dispatch({ type: 'increment' })}>
        +1
      </button>
    </div>
  );
};

createTrackedSelector / zustand

Create useStore

import create from 'zustand';

const useStore = create(() => ({ count: 0 }));

Create useTrackedStore from useStore

import { createTrackedSelector } from 'react-tracked';

const useTrackedStore = createTrackedSelector(useStore);

useTrackedStore in a component

const Counter = () => {
  const state = useTrackedStore();
  const increment = () => {
    useStore.setState((prev) => ({ count: prev.count + 1 }));
  };
  return (
    <div>
      <span>Count: {state.count}</span>
      <button type="button" onClick={increment}>
        +1
      </button>
    </div>
  );
};

Notes with React 18

This library internally uses use-context-selector, a userland solution for useContextSelector hook. React 18 changes useReducer behavior which use-context-selector depends on. This may cause an unexpected behavior for developers. If you see more console.log logs than expected, you may want to try putting console.log in useEffect. If that shows logs as expected, it's an expected behavior. For more information:

API

docs/api

Recipes

docs/recipes

Caveats

docs/caveats

Related projects

docs/comparison

https://github.com/dai-shi/lets-compare-global-state-with-react-hooks

Examples

The examples folder contains working examples. You can run one of them with

PORT=8080 pnpm run examples:01_minimal

and open http://localhost:8080 in your web browser.

You can also try them directly: 01 02 03 04 05 06 07 08 09 10 11 12 13

Benchmarks

See this for details.

Blogs