@saberhq/sail
Version:
Account caching and batched loading for React-based Solana applications.
111 lines (98 loc) • 3.07 kB
text/typescript
import type { PublicKey } from "@solana/web3.js";
import { startTransition, useEffect, useMemo, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import type { FetchKeysFn } from "..";
import {
fetchKeysMaybe,
getCacheKeyOfPublicKey,
SailCacheRefetchError,
useAccountsSubscribe,
useSail,
} from "..";
import type { AccountDatum } from "../types";
const loadKeysFromCache = (
getDatum: (key: PublicKey | null | undefined) => AccountDatum,
keys: (PublicKey | null | undefined)[]
) => {
const ret: Record<string, AccountDatum> = {};
keys.forEach((key) => {
if (key) {
ret[getCacheKeyOfPublicKey(key)] = getDatum(key);
}
});
return ret;
};
/**
* Fetches data of the given accounts.
* @param keys Keys to fetch. Ensure that this is memoized or unlikely to change.
*
* @deprecated use {@link useBatchedParsedAccounts} instead
* @returns One of three types:
* - Buffer -- the account was found
* - null -- account not found or an error occurred while loading the account
* - undefined -- account key not provided or not yet loaded
*/
export const useAccountsData = (
keys: (PublicKey | null | undefined)[]
): readonly AccountDatum[] => {
const { getDatum, onBatchCache, fetchKeys, onError } = useSail();
const [data, setData] = useState<{ [cacheKey: string]: AccountDatum }>(() =>
loadKeysFromCache(getDatum, keys)
);
// TODO: add cancellation
const fetchAndSetKeys = useDebouncedCallback(
async (
fetchKeys: FetchKeysFn,
keys: readonly (PublicKey | null | undefined)[]
) => {
const keysData = await fetchKeysMaybe(fetchKeys, keys);
const nextData: Record<string, AccountDatum> = {};
keys.forEach((key, keyIndex) => {
if (key) {
const keyData = keysData[keyIndex];
if (keyData) {
nextData[getCacheKeyOfPublicKey(key)] = keyData.data;
} else {
nextData[getCacheKeyOfPublicKey(key)] = keyData;
}
}
});
startTransition(() => {
setData(nextData);
});
},
100
);
useEffect(() => {
void (async () => {
await fetchAndSetKeys(fetchKeys, keys)?.catch((e) => {
onError(new SailCacheRefetchError(e, keys));
});
})();
}, [keys, fetchAndSetKeys, fetchKeys, onError]);
useAccountsSubscribe(keys);
// refresh from the cache whenever the cache is updated
useEffect(() => {
return onBatchCache((e) => {
if (keys.find((key) => key && e.hasKey(key))) {
void fetchAndSetKeys(fetchKeys, keys)?.catch((e) => {
onError(new SailCacheRefetchError(e, keys));
});
}
});
}, [keys, fetchAndSetKeys, fetchKeys, onError, onBatchCache]);
// unload debounces when the component dismounts
useEffect(() => {
return () => {
fetchAndSetKeys.cancel();
};
}, [fetchAndSetKeys]);
return useMemo(() => {
return keys.map((key) => {
if (key) {
return data[getCacheKeyOfPublicKey(key)];
}
return key;
});
}, [data, keys]);
};