-
I've created my own custom import { createTrackedSelector } from 'react-tracked';
import { useStore } from 'zustand';
import type { StoreApi } from 'zustand/vanilla';
type ReadonlyStoreApi<T> = Pick<
StoreApi<T>,
'getState' | 'getInitialState' | 'subscribe'
>;
function identity<T>(x: T): T;
function identity(): undefined;
function identity(x?: unknown) {
return x;
}
export const useTrackedStore = <const State, const Selected = State>(
store: ReadonlyStoreApi<State>,
selector: (state: State) => Selected = identity
): Selected => {
const useSelector = <const Tracked>(
tracker: (state: Selected) => Tracked
): Tracked => useStore(store, (state) => tracker(selector(state)));
return createTrackedSelector(useSelector)();
}; I created it so that I can use this with a store that is passed via React Context. It works really well with v4, but with v5 I get a warning about I have read the migration guide, which suggests using const state = useTrackedStore(store, useShallow(selector)); |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 9 replies
-
return createTrackedSelector(useSelector)(); I thought that that would never work. It seems to create new reference. It might have worked by chance. |
Beta Was this translation helpful? Give feedback.
-
Can you provide an example of it not working? Mine works just fine. |
Beta Was this translation helpful? Give feedback.
-
I fixed it. TL;DR: instead of applying the tracking proxy to the value returned by the selector, I apply it to the value returned by import { useCallback, useMemo } from 'react';
import { createProxy, isChanged } from 'proxy-compare';
import { useStoreWithEqualityFn } from 'zustand/traditional';
import type { StoreApi } from 'zustand/vanilla';
type ReadonlyStoreApi<T> = Pick<
StoreApi<T>,
'getState' | 'getInitialState' | 'subscribe'
>;
function identity<T>(x: T): T;
function identity(): undefined;
function identity(x?: unknown) {
return x;
}
export const useTrackedStore = <const State, const Selected = State>(
api: ReadonlyStoreApi<State>,
selector: (state: State) => Selected = identity,
isEqual: NoInfer<(next: Selected, prev: Selected) => boolean> = Object.is
): Selected => {
const affected = useMemo(() => new WeakMap(), []);
const isTrackedEqual = useCallback(
(next: Selected, prev: Selected) =>
isEqual(next, prev) &&
!isChanged(
prev,
next,
affected,
new WeakMap(),
isEqual as (a: unknown, b: unknown) => boolean
),
[affected, isEqual]
);
const slice = useStoreWithEqualityFn(api, selector, isTrackedEqual);
const proxyCache = useMemo(() => new WeakMap(), []);
return createProxy(slice, affected, proxyCache);
}; This also has the benefit of making unwrapping of the proxy much simpler (e.g. for (Note that in practice I've written my own |
Beta Was this translation helpful? Give feedback.
I fixed it.
TL;DR: instead of applying the tracking proxy to the value returned by the selector, I apply it to the value returned by
useStoreWithEqualityFn
, and useproxy-compare
'sisChanged
as part of the equality function.