react-query-external-sync
Version:
A tool for syncing React Query state to an external Dev Tools
174 lines • 8.84 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { useEffect, useMemo, useState } from 'react';
import { useQueries } from '@tanstack/react-query';
import { storageQueryKeys } from './storageQueryKeys';
/**
* Hook that creates individual React Query queries for each AsyncStorage key
* This gives you granular control and better performance since each key has its own query
* Since AsyncStorage doesn't have built-in change listeners, this hook uses polling to detect changes
*
* @example
* // Get individual queries for all AsyncStorage keys
* const queries = useDynamicAsyncStorageQueries({ queryClient });
* // Returns: [
* // { key: '@notifications:status', data: 'enabled', isLoading: false, error: null },
* // { key: '@user:preferences', data: { theme: 'dark' }, isLoading: false, error: null },
* // ...
* // ]
*/
export function useDynamicAsyncStorageQueries({ queryClient, asyncStorage, pollInterval = 1000, enabled = true, }) {
// State to track AsyncStorage keys (since getAllKeys is async)
const [asyncStorageKeys, setAsyncStorageKeys] = useState([]);
// Helper function to get a single AsyncStorage value
const getAsyncStorageValue = useMemo(() => {
return (key) => __awaiter(this, void 0, void 0, function* () {
if (!asyncStorage) {
return null;
}
try {
const value = yield asyncStorage.getItem(key);
if (value === null) {
return null;
}
// Try to parse as JSON, fall back to string
try {
return JSON.parse(value);
}
catch (_a) {
return value;
}
}
catch (error) {
console.error('Error getting AsyncStorage value for key:', key, error);
throw error;
}
});
}, [asyncStorage]);
// Function to refresh the list of AsyncStorage keys
const refreshKeys = useMemo(() => {
return () => __awaiter(this, void 0, void 0, function* () {
if (!enabled || !asyncStorage) {
setAsyncStorageKeys([]);
return;
}
try {
const keys = yield asyncStorage.getAllKeys();
// Filter out React Query cache and other noisy keys
const filteredKeys = keys.filter((key) => !key.includes('REACT_QUERY_OFFLINE_CACHE') && !key.includes('RCTAsyncLocalStorage'));
setAsyncStorageKeys([...filteredKeys]); // Convert readonly array to mutable array
}
catch (error) {
console.error('📱 [AsyncStorage Hook] Error getting AsyncStorage keys:', error);
setAsyncStorageKeys([]);
}
});
}, [enabled, asyncStorage]);
// Initial load of keys
useEffect(() => {
refreshKeys();
}, [refreshKeys]);
// Set up polling for key changes (since AsyncStorage doesn't have listeners)
useEffect(() => {
if (!enabled || pollInterval <= 0 || !asyncStorage) {
return;
}
const interval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
try {
const currentKeys = yield asyncStorage.getAllKeys();
// Filter out React Query cache and other noisy keys
const filteredKeys = currentKeys.filter((key) => !key.includes('REACT_QUERY_OFFLINE_CACHE') && !key.includes('RCTAsyncLocalStorage'));
// Check if keys have changed (added/removed)
const keysChanged = filteredKeys.length !== asyncStorageKeys.length ||
!filteredKeys.every((key) => asyncStorageKeys.includes(key));
if (keysChanged) {
console.log('🔄 [AsyncStorage Hook] AsyncStorage keys changed!');
console.log('🔄 [AsyncStorage Hook] Old keys:', asyncStorageKeys.length);
console.log('🔄 [AsyncStorage Hook] New keys:', filteredKeys.length);
setAsyncStorageKeys([...filteredKeys]); // Convert readonly array to mutable array
// Invalidate all AsyncStorage queries to refresh data
queryClient.invalidateQueries({
queryKey: storageQueryKeys.async.root(),
});
}
else {
// Keys are the same, but check if any values have changed
for (const key of asyncStorageKeys) {
try {
// Check if the query exists in the cache first
const queryExists = queryClient.getQueryCache().find({ queryKey: storageQueryKeys.async.key(key) });
// If query doesn't exist (e.g., after cache clear), skip comparison
// The useQueries hook will recreate the query automatically
if (!queryExists) {
continue;
}
// Get current value from AsyncStorage
const currentValue = yield getAsyncStorageValue(key);
// Get cached value from React Query
const cachedData = queryClient.getQueryData(storageQueryKeys.async.key(key));
// Only compare if we have cached data (avoid false positives after cache clear)
if (cachedData !== undefined) {
// Compare values (deep comparison for objects)
const valuesAreDifferent = JSON.stringify(currentValue) !== JSON.stringify(cachedData);
if (valuesAreDifferent) {
console.log('🔄 [AsyncStorage Hook] Value changed for key:', key);
// Invalidate this specific query
queryClient.invalidateQueries({
queryKey: storageQueryKeys.async.key(key),
});
}
}
}
catch (error) {
console.error('📱 [AsyncStorage Hook] Error checking value for key:', key, error);
}
}
}
}
catch (error) {
console.error('📱 [AsyncStorage Hook] Error polling AsyncStorage keys:', error);
}
}), pollInterval);
return () => {
clearInterval(interval);
};
}, [pollInterval, asyncStorageKeys, queryClient, getAsyncStorageValue, enabled, asyncStorage]);
// Create individual queries for each key
const queries = useQueries({
queries: enabled && asyncStorage
? asyncStorageKeys.map((key) => ({
queryKey: storageQueryKeys.async.key(key),
queryFn: () => __awaiter(this, void 0, void 0, function* () {
const value = yield getAsyncStorageValue(key);
return value;
}),
staleTime: pollInterval > 0 ? pollInterval / 2 : 0, // Half the poll interval
gcTime: 5 * 60 * 1000, // 5 minutes
networkMode: 'always',
retry: 0, // Retry failed requests
retryDelay: 100, // 1 second delay between retries
}))
: [],
combine: (results) => {
if (!enabled || !asyncStorage) {
return [];
}
const combinedResults = results.map((result, index) => ({
key: asyncStorageKeys[index],
data: result.data,
isLoading: result.isLoading,
error: result.error,
}));
return combinedResults;
},
}, queryClient);
return queries;
}
//# sourceMappingURL=useDynamicAsyncStorageQueries.js.map