Skip to main content

API

There are two main functions exported from the library, createContainer and createTrackedSelector.

createTrackedSelector is a building block function which takes a useSelector hook and creates a useTrackedSelector hook.

createContainer is a higher level function for React Context-like usage, which creates a provider and other hooks.

createTrackedSelector

This is a function to create a hook with state usage tracking. It takes one argument useSelector, which is provided by react-redux or any other hooks with the same signature and behavior.

The created hook useTrackedSelector (or whatever named) is a hook that returns state wrapped by Proxies for usage tracking. It behaves the same as useTrackedState from createContainer.

import { useSelector } from 'react-redux'; // or any similar library
import { createTrackedSelector } from 'react-tracked';

const useTrackedSelector = createTrackedSelector(useSelector);

const Component = () => {
const state = useTrackedSelector();
// ...
};

createContainer

It takes two arguments, the first argument is useValue, which is a hook that returns a tuple [state, update]. Typically, it's with useReducer or useState, but it can be any custom hooks based on them.

The second argument is options.

type Options = {
defaultState?: State;
defaultUpdate?: Update;
stateContextName?: string;
updateContextName?: string;
concurrentMode?: boolean;
};
  • defaultState and defaultUpdate are to specify context default values in case you want something without Provider.
  • stateContextName and updateContextName are to specify context display names.
  • concurrentMode is to enable using useContextUpdate from use-context-selector for concurrent features, available since React 18.

Note: you can create multiple containers in one app.

import { createContainer } from 'react-tracked';

const useValue = (props) => useReducer(...);

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

Provider

The Provider returned by createContainer has to be put in the parent component. Typically, it's close to the root component, but it can be (sometimes desirably) lower in the component tree.

const App = (props) => <Provider {...props}>...</Provider>;

useTracked

The useTracked hook returned by createContainer is the recommended hook. It simply returns the [state, update] tuple that useValue returns. The state is wrapped by Proxies for usage tracking.

const Component = () => {
const [state, dispatch] = useTracked();
// ...
};

useUpdate

The useUpdate hook returned by createContainer is for update from useValue; It's named "update" ambiguously, but typically it would be renamed to "dispatch" for useReducer, "setState" for useState, or any "update" function.

const Component = () => {
const dispatch = useUpdate();
// ...
};

useTrackedState

The useTrackedState hook returned by createContainer is for state from useValue; This is wrapped by Proxies as same as useTracked. Use this hook if you don't need update. This hook is compatible with reactive-react-redux.

const Component = () => {
const state = useTrackedState();
// ...
};

useSelector

The useSelector hook returned by createContainer is an optional hook. Use this hook if state usage tracking doesn't work or fit well. This hook is compatible with react-redux. It would ease transition from/to react-redux apps.

const Component = () => {
const selected = useSelector(selector);
// ...
};

memo

memo is removed in v2. With v2, we can use React.memo. The description below only applies to v1.

There is a utility function exported from the library.

This should be used instead of React.memo if props include objects being tracked. Otherwise, usage tracking may not work correctly because a memoized component doesn't always render when a parent component renders.

import { memo } from 'react-tracked'; // v1 only

const ChildComponent = memo(({ num1, str1, obj1, obj2 }) => {
// ...
});

getUntrackedObject

There are some cases when we need to get an original object instead of a tracked object. Although it's not a recommended pattern, the library exports a function as an escape hatch.

import { getUntrackedObject } from 'react-tracked';

const Component = () => {
const state = useTrackedState();
const dispatch = useUpdate();
const onClick = () => {
// this leaks a proxy outside render
dispatch({ type: 'FOO', value: state.foo });

// this works as expected
dispatch({ type: 'FOO', value: getUntrackedObject(state.foo) });
};
// ...
};