// import react import { useCallback, useEffect } from 'react'; // api signal import ApiSignal from '../signals/Api'; import UserSignal from '../signals/User'; import AmplitudeSignal from '../signals/Amplitude'; import { useSignal, useLocalSignal } from '../signal'; /** * use results * * @param opts */ const useResults = (api, type = null, opts = {}, listen = null, onResult = (opts, total, items) => {}, sort = null) => { // signals const [sApi] = useSignal(ApiSignal); const [sUser] = useSignal(UserSignal); const [sAmplitude] = useSignal(AmplitudeSignal); // state const [sKey] = useLocalSignal(JSON.stringify(Object.values(opts))); const [sTotal, setTotal] = useLocalSignal(opts?.total || 0); const [sLoading, setLoading] = useLocalSignal(!opts?.items?.length); const [sResetting, setResetting] = useLocalSignal(false); const [sFeed, setFeed, sFeedVal] = useLocalSignal(''); const [sCategories, setCategories] = useLocalSignal(opts?.categories || []); const [sItems, setItems, sItemsVal] = useLocalSignal(opts?.items || []); const [sCalling, setCalling, sCallingVal] = useLocalSignal(false); // load items const loadItems = useCallback(async (opts = {}, reset = false, force = false) => { // check loading if (sCallingVal.get()) return; // check limit if (!reset && sTotal && opts.maximum && (sItems?.length >= opts.maximum)) return; // create key const feedOpts = { ...opts, page : reset ? 0 : Math.ceil(sItems?.length / (opts.limit || opts.maximum || 18)), limit : (opts.limit || opts.maximum || 18), }; // feed to string const feedStr = JSON.stringify({ ...feedOpts, user : sUser?.id, }); // check string if (!force && (feedStr === sFeedVal.get())) return setLoading(sCalling); // load more sAmplitude?.track?.(`${type}:more`, { from : `results:list`, }); // updates setFeed(feedStr); setLoading(true); setCalling(true); // resetting if (reset) { setItems([]); setTotal(0); setResetting(true); } // try/catch try { // load column const result = await api(feedOpts); // check current again if (feedStr !== sFeedVal.get()) return; // get value const newTotal = result.total; const newItems = (result[type] || []).filter((item) => item); const newCategories = (result.categories || []).filter((c) => c); // new categories const newCategoryArray = (reset ? [...newCategories] : [...sCategories, ...newCategories]).reduce((accum, item) => { // check item exists in accum if (!accum.find((a) => a.slug === item.slug)) accum.push(item); // return accum return accum; }, []); // new item arr const newItemArray = (reset ? [...newItems] : [...sItems, ...newItems]).reduce((accum, item) => { // check item exists in accum if (!accum.find((a) => a.id === item.id)) accum.push(item); // return accum return accum; }, []); // on result onResult(opts, sTotal, sItems); // set total setTotal(newTotal); setItems(sort ? [...newItemArray].sort(sort) : [...newItemArray]); setCategories(newCategoryArray); } catch (e) { // log error console.log(e); } finally { // unset calling setLoading(false); setCalling(false); setResetting(false); } }, [sItems, sTotal, sCategories]); /** * on remove */ const onRemove = useCallback((item) => { // new items const newItems = (sItemsVal.get().filter((m) => m.id !== item.id)); // remove from items setItems([ ...(sort ? [...newItems].sort(sort) : [...newItems]), ]); }, []); /** * on item */ const onItem = useCallback((item) => { // check item if (!item) return; // check listen if (!listen) return; // check item if (!listen.filter(item)) return; // items let sItemsValue = sItemsVal.get(); // check item in items const found = sItemsValue.find((m) => (m.id === item.id) || (item.temp && (m.temp === item.temp))); // if found if (found) { // update item Object.keys(item).forEach((key) => { found[key] = item[key]; }); } else { // add image sItemsValue.push(item); } // check sort if (listen.sort) { // sort items sItemsValue = sItemsValue.sort((a, b) => { // get values const aValue = listen.sort(a); const bValue = listen.sort(b); // check values if (aValue > bValue) return listen.asc ? 1 : -1; if (aValue < bValue) return listen.asc ? -1 : 1; return 0; }); } // set images setItems(sort ? [...sItemsValue].sort(sort) : [...sItemsValue]); }, [listen, sort]); // use effect useEffect(() => { // check listen if (!listen) return; if (!listen?.key) return; // listem api const listenApi = listen.api || sApi; // check listen api socket if (!listenApi?.socket?.on) return; // on listenApi.socket.on(listen.key, onItem); // return return () => { // off listenApi.socket.off(listen.key, onItem); }; }, [listen, sApi]); // use effect useEffect(() => { // check options if (opts.disable) return; // load items loadItems(opts, true, true); }, [JSON.stringify(opts || {})]); // return return { key : sKey, feed : sFeed, total : sTotal, items : sItems, loading : sLoading, calling : sCalling, resetting : sResetting, categories : sCategories, onItem, onRemove, loadNext : () => loadItems(opts), }; }; /** * use results */ export default useResults;