From 786d9b6e3182e17e92804d48005c1e975f0f0408 Mon Sep 17 00:00:00 2001 From: schmanu Date: Thu, 10 Oct 2024 11:18:42 +0200 Subject: [PATCH] refactor: use rtk query for loading owned safes --- .../SafeAppLandingPage/AppActions.tsx | 10 ++-- .../welcome/MyAccounts/useAllOwnedSafes.ts | 44 ++++------------- src/hooks/useOwnedSafes.ts | 49 +++---------------- src/store/gateway.ts | 31 +++++++++++- src/tests/pages/apps-share.test.tsx | 12 +++-- 5 files changed, 58 insertions(+), 88 deletions(-) diff --git a/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx b/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx index 60879f5581..4576271c8d 100644 --- a/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx +++ b/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx @@ -1,5 +1,5 @@ import { Box, Button, MenuItem, Select, Typography, Grid, FormControl, InputLabel } from '@mui/material' -import type { ChainInfo, SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' +import type { AllOwnedSafes, ChainInfo, SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' import { useEffect, useMemo, useState } from 'react' import Link from 'next/link' import type { UrlObject } from 'url' @@ -12,9 +12,9 @@ import { parsePrefixedAddress } from '@/utils/addresses' import SafeIcon from '@/components/common/SafeIcon' import EthHashInfo from '@/components/common/EthHashInfo' import { AppRoutes } from '@/config/routes' -import useOwnedSafes from '@/hooks/useOwnedSafes' import { CTA_BUTTON_WIDTH, CTA_HEIGHT } from '@/components/safe-apps/SafeAppLandingPage/constants' import CreateNewSafeSVG from '@/public/images/open/safe-creation.svg' +import useAllOwnedSafes from '@/components/welcome/MyAccounts/useAllOwnedSafes' type Props = { appUrl: string @@ -28,7 +28,7 @@ type CompatibleSafesType = { address: string; chainId: string; shortName?: strin const AppActions = ({ wallet, onConnectWallet, chain, appUrl, app }: Props): React.ReactElement => { const lastUsedSafe = useLastSafe() - const ownedSafes = useOwnedSafes() + const [ownedSafes] = useAllOwnedSafes(wallet?.address) const addressBook = useAppSelector(selectAllAddressBooks) const chains = useAppSelector(selectChains) const compatibleChains = app.chainIds @@ -150,7 +150,7 @@ const AppActions = ({ wallet, onConnectWallet, chain, appUrl, app }: Props): Rea export { AppActions } const getCompatibleSafes = ( - ownedSafes: { [chainId: string]: string[] }, + ownedSafes: AllOwnedSafes, compatibleChains: string[], chainsData: ChainInfo[], ): CompatibleSafesType[] => { @@ -159,7 +159,7 @@ const getCompatibleSafes = ( return [ ...safes, - ...(ownedSafes[chainId] || []).map((address) => ({ + ...(ownedSafes[chainId] ?? []).map((address) => ({ address, chainId, shortName: chainData?.shortName, diff --git a/src/components/welcome/MyAccounts/useAllOwnedSafes.ts b/src/components/welcome/MyAccounts/useAllOwnedSafes.ts index b96e7007cb..9074f3bb73 100644 --- a/src/components/welcome/MyAccounts/useAllOwnedSafes.ts +++ b/src/components/welcome/MyAccounts/useAllOwnedSafes.ts @@ -1,40 +1,14 @@ -import type { AllOwnedSafes } from '@safe-global/safe-gateway-typescript-sdk' -import { getAllOwnedSafes } from '@safe-global/safe-gateway-typescript-sdk' -import type { AsyncResult } from '@/hooks/useAsync' -import useAsync from '@/hooks/useAsync' -import useLocalStorage from '@/services/local-storage/useLocalStorage' -import { useEffect } from 'react' +import { asError } from '@/services/exceptions/utils' +import { useGetAllOwnedSafesQuery } from '@/store/gateway' +import { skipToken } from '@reduxjs/toolkit/query' +import { type AllOwnedSafes } from '@safe-global/safe-gateway-typescript-sdk' +import { useMemo } from 'react' -const CACHE_KEY = 'ownedSafesCache_' +const useAllOwnedSafes = (address: string | undefined): [AllOwnedSafes, Error | undefined, boolean] => { + const { data, error, isLoading } = useGetAllOwnedSafesQuery(address ? { address } : skipToken) -type OwnedSafesPerAddress = { - address: string | undefined - ownedSafes: AllOwnedSafes -} - -const useAllOwnedSafes = (address: string): AsyncResult => { - const [cache, setCache] = useLocalStorage(CACHE_KEY + address) - - const [data, error, isLoading] = useAsync(async () => { - if (!address) - return { - ownedSafes: {}, - address: undefined, - } - const ownedSafes = await getAllOwnedSafes(address) - return { - ownedSafes, - address, - } - }, [address]) - - useEffect(() => { - if (data?.ownedSafes != undefined && data.address === address) { - setCache(data.ownedSafes) - } - }, [address, cache, data, setCache]) - - return [cache, error, isLoading] + const wrappedError = useMemo(() => (error ? asError(error) : undefined), [error]) + return [data ?? {}, wrappedError, isLoading] } export default useAllOwnedSafes diff --git a/src/hooks/useOwnedSafes.ts b/src/hooks/useOwnedSafes.ts index 66db1772b8..e2043c1764 100644 --- a/src/hooks/useOwnedSafes.ts +++ b/src/hooks/useOwnedSafes.ts @@ -1,52 +1,15 @@ -import { useEffect } from 'react' -import { getOwnedSafes, type OwnedSafes } from '@safe-global/safe-gateway-typescript-sdk' - -import useLocalStorage from '@/services/local-storage/useLocalStorage' import useWallet from '@/hooks/wallets/useWallet' -import { Errors, logError } from '@/services/exceptions' import useChainId from './useChainId' +import { useGetOwnedSafesQuery } from '@/store/gateway' +import { skipToken } from '@reduxjs/toolkit/query' -const CACHE_KEY = 'ownedSafes' - -type OwnedSafesCache = { - [walletAddress: string]: { - [chainId: string]: OwnedSafes['safes'] - } -} - -const useOwnedSafes = (): OwnedSafesCache['walletAddress'] => { +const useOwnedSafes = (): string[] => { const chainId = useChainId() - const { address: walletAddress } = useWallet() || {} - const [ownedSafesCache, setOwnedSafesCache] = useLocalStorage(CACHE_KEY) - - useEffect(() => { - if (!walletAddress || !chainId) return - let isCurrent = true - - /** - * No useAsync in this case to avoid updating - * for a new chainId with stale data see https://github.com/safe-global/safe-wallet-web/pull/1760#discussion_r1133705349 - */ - getOwnedSafes(chainId, walletAddress) - .then( - (ownedSafes) => - isCurrent && - setOwnedSafesCache((prev) => ({ - ...prev, - [walletAddress]: { - ...(prev?.[walletAddress] || {}), - [chainId]: ownedSafes.safes, - }, - })), - ) - .catch((error: Error) => logError(Errors._610, error.message)) + const { address } = useWallet() || {} - return () => { - isCurrent = false - } - }, [chainId, walletAddress, setOwnedSafesCache]) + const { data: ownedSafes } = useGetOwnedSafesQuery(address ? { chainId, address } : skipToken) - return ownedSafesCache?.[walletAddress || ''] ?? {} + return ownedSafes?.safes ?? [] } export default useOwnedSafes diff --git a/src/store/gateway.ts b/src/store/gateway.ts index 3b027245b9..04dccddb56 100644 --- a/src/store/gateway.ts +++ b/src/store/gateway.ts @@ -1,6 +1,13 @@ import { createApi } from '@reduxjs/toolkit/query/react' -import { getTransactionDetails, type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk' +import { + type AllOwnedSafes, + type OwnedSafes, + getAllOwnedSafes, + getOwnedSafes, + getTransactionDetails, + type TransactionDetails, +} from '@safe-global/safe-gateway-typescript-sdk' import type { BaseQueryFn } from '@reduxjs/toolkit/dist/query/baseQueryTypes' import type { FetchBaseQueryError } from '@reduxjs/toolkit/dist/query/react' import { getDelegates } from '@safe-global/safe-gateway-typescript-sdk' @@ -48,6 +55,26 @@ export const gatewayApi = createApi({ } }, }), + getOwnedSafes: builder.query({ + async queryFn({ chainId, address }) { + try { + const ownedSafes = await getOwnedSafes(chainId, address) + return { data: ownedSafes } + } catch (error) { + return { error: error as FetchBaseQueryError } + } + }, + }), + getAllOwnedSafes: builder.query({ + async queryFn({ address }) { + try { + const ownedSafes = await getAllOwnedSafes(address) + return { data: ownedSafes } + } catch (error) { + return { error: error as FetchBaseQueryError } + } + }, + }), }), }) @@ -56,4 +83,6 @@ export const { useGetMultipleTransactionDetailsQuery, useLazyGetTransactionDetailsQuery, useGetDelegatesQuery, + useGetAllOwnedSafesQuery, + useGetOwnedSafesQuery, } = gatewayApi diff --git a/src/tests/pages/apps-share.test.tsx b/src/tests/pages/apps-share.test.tsx index c6193f7cdd..db5b5e3ef8 100644 --- a/src/tests/pages/apps-share.test.tsx +++ b/src/tests/pages/apps-share.test.tsx @@ -3,7 +3,7 @@ import { render, screen, waitFor } from '../test-utils' import ShareSafeApp from '@/pages/share/safe-app' import { CONFIG_SERVICE_CHAINS } from '@/tests/mocks/chains' import * as useWalletHook from '@/hooks/wallets/useWallet' -import * as useOwnedSafesHook from '@/hooks/useOwnedSafes' +import * as useAllOwnedSafesHook from '@/components/welcome/MyAccounts/useAllOwnedSafes' import * as manifest from '@/services/safe-apps/manifest' import * as sdk from '@safe-global/safe-gateway-typescript-sdk' import crypto from 'crypto' @@ -214,9 +214,13 @@ describe('Share Safe App Page', () => { label: 'Metamask', chainId: '1', })) - jest.spyOn(useOwnedSafesHook, 'default').mockImplementation(() => ({ - '1': [safeAddress], - })) + jest.spyOn(useAllOwnedSafesHook, 'default').mockImplementation(() => [ + { + '1': [safeAddress], + }, + undefined, + false, + ]) render(, { routerProps: {