npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-tracked

v2.0.1

Published

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

Downloads

499,224

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:

  • https://github.com/dai-shi/use-context-selector/issues/100
  • https://github.com/dai-shi/react-tracked/issues/177

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