I have a React hook that receives a list of file URLs, which it downloads and saves into a local cache. The challenge I face is that I cannot figure out a good way to listen for changes in the cacheQueue
state. When a new item is added to the cacheQueue
, I would like to receive it in the useEffect and submit it to the cacheQueue
. However, the challenge is that I make some changes to the cacheQueue within the useEffect
to reflect the item's cache state, and this creates a cycle that goes on forever. How can I write my useEffect to avoid this endless loop?
const useExternalCache = () => {const [filePath, setFilePath] = useState('');const [cacheQueue, setCacheQueue] = useImmer<CacheQueue[]>([]);const handleAdd = useCallback((queueItem: CacheQueue) => { setCacheQueue((draft) => { if (!draft.find((item) => item.uri === queueItem.uri)) { draft.push(queueItem); } });}, []);const handleAddAll = useCallback( (queueItems: CacheQueue[]) => { setCacheQueue((draft) => { const unique = Lodash.xorBy(queueItems, draft, 'uri'); if (!Lodash.isEqual(unique, draft)) { unique.forEach(handleAdd); } }); }, [handleAdd],);const updateCacheQueueItem = useCallback((update: CacheQueue) => { setCacheQueue((draft: CacheQueue[]) => { const toUpdate = draft.find((item) => update.uri === item.uri); if (toUpdate) { const idx = draft.indexOf(toUpdate); const current = draft[idx]; draft[idx] = {...current, ...update}; } });}, []);const clearQueue = useCallback(() => { setCacheQueue((draft: CacheQueue[]) => { draft.splice(0, draft.length); });}, []);const onAddToQueueDone = async ({ id: uri, result,}: { id: string; result?: Promise<string>;}) => { const cacheURI = await result; cacheURI && setFilePath(cacheURI);};const addItemsToCacheQueue = useAsyncQueue({ concurrency, done: onAddToQueueDone,});const getPathByUri = useMemo( () => async (uri: string, headers?: any) => { const result = await cache.getPathByUri(uri, headers); if (result) { const updatedUri = Platform.OS === 'android' ? 'file://'+ result : result; return updatedUri; } return result; }, [],);const addToCacheQueue = useCallback( ({uri, headers}: CacheQueue) => addItemsToCacheQueue.add({ id: uri, task: async () => { //Update item to reflect it is in caching queue updateCacheQueueItem({ uri, isCaching: true, }); const result = await getPathByUri(uri, headers); //Update item to reflect it is cached updateCacheQueueItem({ uri, isCaching: false, isCached: true, }); return result; }, }), [addItemsToCacheQueue, updateCacheQueueItem],);useEffect(() => { console.log('cacheQueue changed: ', cacheQueue); const newlyAdded = Lodash.filter( cacheQueue, ({isCaching, isCached}) => !(isCaching || isCached), ); console.log('BgetPathByUri: adding', newlyAdded.length); Lodash.forEach(newlyAdded, (item) => addToCacheQueue(item));}, [cacheQueue]);return {handleAddAll, filePath, getPathByUri};
};