import { useEffect, useRef, useState } from 'react'; import { type Store } from './createStore'; import { shallowEqual } from '../utils/shallowEqual'; export function useStoreSelector( store: Store, selector: (state: TState) => TSlice, equalityFn: (a: TSlice, b: TSlice) => boolean = Object.is ): TSlice { const latestSelectorRef = useRef(selector); const latestEqualityRef = useRef(equalityFn); latestSelectorRef.current = selector; latestEqualityRef.current = equalityFn; const [slice, setSlice] = useState(() => latestSelectorRef.current(store.getState()) ); useEffect(() => { function handleChange(nextState: TState, prevState: TState) { const nextSlice = latestSelectorRef.current(nextState); const prevSlice = latestSelectorRef.current(prevState); if (!latestEqualityRef.current(nextSlice, prevSlice)) { setSlice(nextSlice); } } const unsubscribe = store.subscribe(handleChange); // Sync once in case state changed between render and effect. Use the // functional updater form so we compare against the already-stored slice // rather than calling the selector twice on the same state (which returns // different object references for non-primitive values and causes an // unnecessary re-render that leads to infinite update loops with virtualizers). setSlice((prev) => { const current = latestSelectorRef.current(store.getState()); return latestEqualityRef.current(prev, current) ? prev : current; }); return unsubscribe; }, [store]); return slice; } export const shallow = shallowEqual;