import * as React from "react"; import type { StoreApi, UseBoundStore } from "zustand"; import { useSuffixContext } from "../contexts"; import { shallow } from "../utils"; import type { State } from "./useStore"; export const useConnectedEffect = ( store: UseBoundStore>, effect: (currentSlice: any, prevSlice: any) => () => void | undefined, ids: string[], deps: React.DependencyList = [], ) => { const suffix = useSuffixContext(); const selector = React.useCallback( (currentData: State) => { return ids.map((id) => { const key = suffix ? `${id}-${suffix}` : id; return currentData[key]; }); }, [ids, suffix], ); const currentSlice = React.useRef(selector(store.getState())); const cleanup = React.useRef<() => void>(undefined); const listener = React.useCallback(() => { const nextSlice = selector(store.getState()); if (!shallow(currentSlice.current, nextSlice)) { const prevSlice = currentSlice.current; if (currentSlice) { currentSlice.current = nextSlice; } if (cleanup) { cleanup.current?.(); const result = effect(currentSlice.current, prevSlice); if (cleanup && typeof result === "function") { cleanup.current = result; } } } }, [selector, effect, store]); React.useEffect(() => { // if effect is an async function, then it will return a promise and we don't want to call cleanup const result = effect(currentSlice.current, currentSlice.current); if (cleanup && typeof result === "function") { cleanup.current = result; } const storeCleanup = store.subscribe(listener); return () => { cleanup?.current?.(); storeCleanup?.(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, deps); // fn to manually re-trigger return React.useCallback(() => { cleanup?.current?.(); const result = effect(currentSlice.current, currentSlice.current); if (cleanup && typeof result === "function") { cleanup.current = result; } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); };