Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

react-tracked

dai-shi658.2kMIT2.0.1TypeScript support: included

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

react, redux, state, hooks

readme

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

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

changelog

Change Log

[Unreleased]

[2.0.1] - 2024-09-10

Changed

  • fix: check global process at module level #215

[2.0.0] - 2024-05-07

Changed

  • Module-first setup #209

Added

  • breaking: make it compatible with memo and react compiler #202

Removed

  • memo is removed as it is no longer necessary with #202

[1.7.14] - 2024-03-08

Changed

  • update use-context-selector v1.4.4

[1.7.13] - 2024-03-06

Changed

  • update use-context-selector v1.4.3

[1.7.12] - 2024-03-02

Changed

  • update use-context-selector #200

[1.7.11] - 2023-01-04

Changed

  • Update proxy-compare v2.4.0 #170

[1.7.10] - 2022-08-13

Changed

  • Update proxy-compare
  • Update use-context-selector

[1.7.9] - 2022-04-17

Changed

  • fix: extra re-renders in dev mode #148

[1.7.8] - 2022-03-18

Changed

  • fix: make default context values customizable #143
  • Update proxy-compare

[1.7.7] - 2022-03-09

Changed

  • fix: improve no provider error #139

[1.7.6] - 2022-01-12

Changed

  • feat: Added container name optional args when creating containers (#132)

[1.7.5] - 2021-11-14

Changed

  • Update dependencies fixing ESM exports (#125)
  • fix: eliminate useLayoutEffect (#118)

[1.7.4] - 2021-08-13

Changed

  • Fix package.json properly for ESM (#115)

[1.7.3] - 2021-07-01

Changed

  • Fix anonymous functions shown in devtools (#108)

[1.7.2] - 2021-06-03

Changed

  • Fix useTracked for legacy mode following #92 (#102)

[1.7.1] - 2021-05-15

Changed

  • Update proxy-compare and drop unstable options (#96)

[1.7.0] - 2021-04-11

Changed

  • createContainer takes the second argument for opt-in concurrent mode support (#92)
    • This is a (technically breaking) behavioral change.

[1.6.6] - 2021-04-05

Changed

  • Better SSR detection for Deno (#89)

[1.6.5] - 2021-01-24

Changed

  • Update use-context-selector to fix some behaviors

[1.6.4] - 2021-01-21

Changed

  • Fix read-only and non-configurable data property error (#81)

[1.6.3] - 2021-01-17

Changed

  • Support memo with forwarded ref (#80)

[1.6.2] - 2021-01-14

Changed

  • Improve memo typing (#77)

[1.6.1] - 2020-12-30

Changed

  • Fix a hypothetical issue of reusing deepChangedCache

[1.6.0] - 2020-12-26

Added

  • Export createTrackedSelector, a new building-block function (#71)

[1.5.1] - 2020-12-19

Changed

  • Fix a fatal bug in v1.5.0 (#72)

[1.5.0] - 2020-12-04

Changed

  • Refactor: typescript, proxy-compare, use-context-selector (#68)

[1.4.2] - 2020-09-08

Changed

  • Fix invalid type definitions (#59)

[1.4.1] - 2020-06-23

Changed

  • Use unstable_batchedUpdates internally (#54)

[1.4.0] - 2020-05-13

Changed

  • Export a special memo instead of trackMemo (#47)
    • This is a breaking change in API and requires migration

[1.3.0] - 2020-03-07

Changed

  • Notify child components in update not in render (#42)
    • No updates on props change (breaking change in an undocumented behavior)
    • The update must be a function (breaking change in an example and a recipe)

[1.2.0] - 2020-02-29

Changed

  • Fix anonymous hook names (#39)
  • Add debug value to show tracked paths in useTrackedState (#40)
  • Unwrap Proxy before wrapping to mitigate possible pitfalls (#41)

[1.1.1] - 2020-02-26

Changed

  • Use useIsomorphicLayoutEffect in Provider to eliminate SSR warning

[1.1.0] - 2020-02-24

Changed

  • A workaround for React render warning (hopefully temporarily)

[1.0.6] - 2020-02-11

Changed

  • Betect SSR beter by checking userAgent

[1.0.5] - 2020-02-03

Changed

  • Ignore thrown error/promise in useTrackedState callback (for Suspense)

[1.0.4] - 2020-02-02

Changed

  • Improve internal mode for deepProxy behavior
    • This doesn't change the default behavior

[1.0.3] - 2020-02-01

Changed

  • Fix typing of a readonly tuple in 1.0.2
  • Fix typing of useValue in createContainer

[1.0.2] - 2020-01-19

Changed

  • Change useValue type to accept a readonly tuple

[1.0.1] - 2020-01-05

Changed

  • Possibly reduce bundle size

[1.0.0] - 2019-12-05

Changed

  • Fix API and release v1

[0.11.0] - 2019-10-12

Added

  • A new API getUntrackedObject as an escape hatch (#24)

[0.10.0] - 2019-10-08

Added

  • A new API trackMemo as an escape hatch for React.memo (#22)

[0.9.0] - 2019-10-05

Changed

  • Inline useForceUpdate to remove unnecessary deps

[0.8.0] - 2019-09-05

Changed

  • Only provide container API (breaking change) (#16)

[0.7.0] - 2019-07-20

Changed

  • No useLayoutEffect for invoking listeners (which leads de-opt sync mode)

[0.6.0] - 2019-07-15

Changed

  • Prefer createContainer to default context (#5)

[0.5.0] - 2019-07-13

Changed

  • Warn if useValue is not statically defined

[0.4.0] - 2019-06-14

Added

  • Add createContainer

Changed

  • Rename to simple provider (breaking change)

[0.3.0] - 2019-06-13

Changed

  • Add customContext support
  • Fix useDispatch
  • Remove unnecessary batchedUpdates
  • Split useTrackedState and useTracked

[0.2.0] - 2019-06-13

Changed

  • Properly useEffect in TrackedProvider

[0.1.0] - 2019-06-12

Added

  • Initial experimental release