# react-tracked: Proxy-based state usage tracking for React

> react-tracked watches which properties a component actually reads and re-renders only when those properties change. It ships two APIs, createContainer and createTrackedSelector, so the same tracking can sit on top of useState, React Redux or Zustand.

**dai-shi/react-tracked** — State usage tracking with Proxies. Optimize re-renders for useState/useReducer, React Redux, Zustand and others.

- Repository: https://github.com/dai-shi/react-tracked
- Website: https://react-tracked.js.org
- Stars: 2,821 · Forks: 71
- Language: TypeScript
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/dai-shi-react-tracked

## The re-render problem react-tracked is built around

React Context re-renders every consumer when any part of the context value changes. The README states this plainly: useContext triggers re-renders whenever a small part of the state object changes, and that becomes a performance problem once an app has a central global state used in many components. Redux solves the same problem with selectors, but the README argues that using selectors purely for performance is not the best fit, because they require understanding object reference equality, which is non-trivial for beginners and still difficult for complex structures. That is the gap react-tracked targets. It is for teams that want to keep one shared state object and stop writing selector functions whose only job is to avoid a re-render. It is not for a component that already reads one primitive field, because there is nothing to narrow.

## How the Proxy tracking actually works

The mechanism is property access tracking. react-tracked wraps the state object in a Proxy, records which properties a component reads during render, and re-renders that component only when one of the accessed properties changes. The README notes this works at the root level and on deep nested objects, not just the first layer. The library exposes two entry points, both of which take a hook and return a hook or a container holding one. createContainer takes a hook that returns a [state, dispatch] tuple, such as useState or useReducer, and returns a Provider plus a useTracked hook. The useTracked hook returns the same tuple, except the state is Proxy-wrapped and the setter is wrapped as well. createTrackedSelector takes an existing selector hook and returns a tracked version of it, which is how the same tracking is applied to React-Redux useSelector or a Zustand store hook. Internally the README says the library uses use-context-selector, a userland implementation of a useContextSelector hook.

## Installing react-tracked and a first tracked counter

The package declares react and scheduler as peer dependencies, so the README instructs you to install all three yourself. The version in the repository package.json is 2.0.1, published as an ES module with a CommonJS build under dist/cjs.

```bash
npm install react-tracked react scheduler
```

A first use follows the createContainer path in the README. Define a hook that returns a tuple, then hand it to createContainer. This snippet is copied from the README example.

```js
import { useState } from 'react';
import { createContainer } from 'react-tracked';

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

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

Inside a component, useTracked returns that tuple. Reading state.count during render registers the access, so the component re-renders when count changes and not when text changes.

```jsx
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>
  );
};
```

Wrap the tree in the returned Provider. The README shows exactly this, with a Counter and a TextBox as siblings.

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

The repository also carries fifteen examples under examples/, and the README gives a command for running one locally with pnpm, for example PORT=8080 pnpm run examples:01_minimal, then opening http://localhost:8080. Several are also linked as StackBlitz playgrounds.

## Where the Proxy approach breaks down

The README keeps a caveats page, and the React 18 note is the sharpest limitation it states. Because react-tracked depends on use-context-selector, and React 18 changed useReducer behavior that use-context-selector relies on, the README warns this may cause unexpected behavior. The symptom it describes is more console.log output than you expect, and the suggested check is to move the log into a useEffect; if the log then appears as expected, the extra render-time logging is expected behavior rather than a bug. Two issue links are given for the detail. That is a real cost of the design: tracking happens during render, so render-phase side effects like logging do not behave the way an untracked component would. The other boundary is structural. createTrackedSelector only helps where a selector hook already exists to wrap; if your state lives in a plain object passed down through props, there is nothing for it to attach to.

## react-tracked against plain selector hooks

The closest alternative is the selector you would otherwise write by hand, and the repository treats this as a direct comparison rather than a side note. The README points to a separate project, lets-compare-global-state-with-react-hooks, and the site has a comparison page under website/docs. The difference in approach is where the work sits. A hand-written selector makes you name the exact slice and reason about reference equality, which is explicit and predictable but has to be repeated for every component and every nested field. react-tracked moves that decision into the render pass: you read state.count, and the library infers the dependency. The trade is transparency for convenience. With a selector, what a component depends on is visible in the code. With tracking, the dependency set is whatever the render happened to touch, which can change when a conditional branch changes. Valtio, also by the same author, is named in the related searches around this project, and it shares the Proxy idea, but react-tracked's distinguishing feature is that it wraps hooks you already have instead of introducing a store of its own.

## Maintenance, licence and the cost of upgrading

The repository is not archived, and the last push was on 2026-05-19. The licence is MIT, which permits commercial use and modification provided the copyright notice and permission notice are retained; this is a description of the licence text, not legal advice, so read LICENSE in the repository for the binding terms. On upgrade cost, the package.json shows the project at 2.0.1 and the README frames v1.6.0 as the release that added createTrackedSelector as a building-block API on top of the earlier context-replacement role, so the 1.x to 2.x line is where the API surface widened. The build is split into ESM and CommonJS outputs with separate type declarations under dist and dist/cjs, and the package sets sideEffects to false, which matters if your bundler relies on that flag for tree shaking. The repository does not document a rollback path if a tracking change causes extra renders in production, so the practical check is the React 18 console behavior described in the README before you ship an upgrade.

## Conclusion

Adopt react-tracked if you already have a single global state object shared by many components and selector maintenance has become the hard part, especially if you want the same tracking to work across useState, React Redux and Zustand through createTrackedSelector. Do not adopt it if your components already select narrow primitives with a plain selector, since the Proxy adds a layer without removing work. Before committing, open the caveats and recipes pages under website/docs, and check the React 18 useReducer note against the console output your own app produces.

## FAQ

### How do I install react-tracked?

Run npm install react-tracked react scheduler. The README states that react and scheduler are peer dependencies and that you install them yourself.

### What is react-tracked used for?

It tracks which properties of a state object a component reads and re-renders only when those properties change. The README describes this as state usage tracking, implemented with Proxies, and says it works on deep nested objects as well as the root level.

### Does react-tracked work with React Redux and Zustand?

Yes. createTrackedSelector takes a selector hook and returns a tracked version, and the README shows it applied to React-Redux useSelector and to a Zustand store hook.

### Why does react-tracked log more console output than expected on React 18?

The README says React 18 changed useReducer behavior that use-context-selector depends on, which may cause unexpected behavior. It suggests putting console.log in useEffect; if the logs then appear as expected, the extra render-time logging is expected behavior.

## Sources

- [dai-shi/react-tracked on GitHub](https://github.com/dai-shi/react-tracked)
- [Issues](https://github.com/dai-shi/react-tracked/issues)
- [License: MIT](https://github.com/dai-shi/react-tracked/blob/main/LICENSE)
- [Project website](https://react-tracked.js.org)
- [README](https://github.com/dai-shi/react-tracked/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dai-shi-react-tracked
