From 21dfb2dd2a567aa8792b58583c2b06aaf51884dc Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Mon, 30 Oct 2023 19:37:33 +0000 Subject: [PATCH] use nanostores --- .../Web3/components/Alert/Alert.test.tsx | 2 +- .../components/Conversion/Conversion.test.tsx | 4 +- .../Web3/components/Conversion/Conversion.tsx | 2 +- src/features/Web3/components/Form/index.tsx | 2 +- .../Web3/components/Input/InputGroup.test.tsx | 18 +------- .../Web3/components/Send/SendErc20.tsx | 4 +- .../Web3/components/TokenSelect/Token.tsx | 2 +- .../components/TokenSelect/TokenSelect.tsx | 20 ++++---- .../Web3/hooks/useTokens/getTokens.ts | 17 ------- src/features/Web3/hooks/useTokens/index.tsx | 2 - .../Web3/hooks/useTokens/useTokens.tsx | 46 ------------------- src/features/Web3/hooks/useTokensStore.tsx | 34 ++++++++++++++ src/features/Web3/stores/tokens/fetcher.ts | 12 +++++ src/features/Web3/stores/tokens/index.ts | 3 ++ .../Web3/stores/{ => tokens}/selectedToken.ts | 6 +-- src/features/Web3/stores/tokens/tokens.ts | 12 +++++ .../useTokens => stores/tokens}/types.ts | 0 17 files changed, 86 insertions(+), 100 deletions(-) delete mode 100644 src/features/Web3/hooks/useTokens/getTokens.ts delete mode 100644 src/features/Web3/hooks/useTokens/index.tsx delete mode 100644 src/features/Web3/hooks/useTokens/useTokens.tsx create mode 100644 src/features/Web3/hooks/useTokensStore.tsx create mode 100644 src/features/Web3/stores/tokens/fetcher.ts create mode 100644 src/features/Web3/stores/tokens/index.ts rename src/features/Web3/stores/{ => tokens}/selectedToken.ts (72%) create mode 100644 src/features/Web3/stores/tokens/tokens.ts rename src/features/Web3/{hooks/useTokens => stores/tokens}/types.ts (100%) diff --git a/src/features/Web3/components/Alert/Alert.test.tsx b/src/features/Web3/components/Alert/Alert.test.tsx index a3204dee..4f9f09cc 100644 --- a/src/features/Web3/components/Alert/Alert.test.tsx +++ b/src/features/Web3/components/Alert/Alert.test.tsx @@ -3,7 +3,7 @@ import { describe, it } from 'vitest' import Alert from './Alert' describe('Alert', () => { - it('renders without crashing', async () => { + it('renders without crashing', () => { render( { - it('renders without crashing', async () => { - render() + it('renders without crashing', () => { + render() }) }) diff --git a/src/features/Web3/components/Conversion/Conversion.tsx b/src/features/Web3/components/Conversion/Conversion.tsx index ddfdc9fb..19aac398 100644 --- a/src/features/Web3/components/Conversion/Conversion.tsx +++ b/src/features/Web3/components/Conversion/Conversion.tsx @@ -1,6 +1,6 @@ import { useEffect, type ReactElement, useState } from 'react' import styles from './Conversion.module.css' -import { $selectedToken } from '../../stores/selectedToken' +import { $selectedToken } from '../../stores/tokens/selectedToken' import { useStore } from '@nanostores/react' export function Conversion({ amount }: { amount: string }): ReactElement { diff --git a/src/features/Web3/components/Form/index.tsx b/src/features/Web3/components/Form/index.tsx index 2f7562f4..0ae17b4f 100644 --- a/src/features/Web3/components/Form/index.tsx +++ b/src/features/Web3/components/Form/index.tsx @@ -8,7 +8,7 @@ import styles from './index.module.css' import { SendNative, SendErc20 } from '../Send' import { useSend } from '../../hooks/useSend' import type { SendFormData } from './types' -import { $selectedToken } from '../../stores/selectedToken' +import { $selectedToken } from '../../stores/tokens/selectedToken' import { useStore } from '@nanostores/react' export default function Web3Form(): ReactElement { diff --git a/src/features/Web3/components/Input/InputGroup.test.tsx b/src/features/Web3/components/Input/InputGroup.test.tsx index 67734af0..c073ff85 100644 --- a/src/features/Web3/components/Input/InputGroup.test.tsx +++ b/src/features/Web3/components/Input/InputGroup.test.tsx @@ -6,14 +6,7 @@ const setAmount = vi.fn() describe('InputGroup', () => { it('renders without crashing', async () => { - render( - - ) + render() const input = await screen.findByRole('textbox') const button = await screen.findByRole('button') @@ -23,14 +16,7 @@ describe('InputGroup', () => { }) it('renders disabled', async () => { - render( - - ) + render() const input = await screen.findByRole('textbox') expect(input).toBeDefined() diff --git a/src/features/Web3/components/Send/SendErc20.tsx b/src/features/Web3/components/Send/SendErc20.tsx index 5ea0e8d3..efef832e 100644 --- a/src/features/Web3/components/Send/SendErc20.tsx +++ b/src/features/Web3/components/Send/SendErc20.tsx @@ -4,7 +4,7 @@ import siteConfig from '@config/blog.config' import { abiErc20Transfer } from './abiErc20Transfer' import { useEffect } from 'react' import { useStore } from '@nanostores/react' -import { $selectedToken } from '@features/Web3/stores/selectedToken' +import { $selectedToken } from '@features/Web3/stores/tokens/selectedToken' export function SendErc20({ amount, @@ -16,7 +16,7 @@ export function SendErc20({ const selectedToken = useStore($selectedToken) const { config } = usePrepareContractWrite({ - address: selectedToken.address, + address: selectedToken?.address, abi: abiErc20Transfer, functionName: 'transfer', args: [ diff --git a/src/features/Web3/components/TokenSelect/Token.tsx b/src/features/Web3/components/TokenSelect/Token.tsx index d951b062..2d87bcae 100644 --- a/src/features/Web3/components/TokenSelect/Token.tsx +++ b/src/features/Web3/components/TokenSelect/Token.tsx @@ -3,7 +3,7 @@ import * as Select from '@radix-ui/react-select' import { formatCurrency } from '@coingecko/cryptoformat' import './Token.css' import { Check } from '@images/components/react' -import type { GetToken } from '../../hooks/useTokens' +import type { GetToken } from '@features/Web3/stores/tokens' interface SelectItemProps extends HTMLAttributes { token: GetToken | undefined diff --git a/src/features/Web3/components/TokenSelect/TokenSelect.tsx b/src/features/Web3/components/TokenSelect/TokenSelect.tsx index 18a0a2e8..eaa6a4a1 100644 --- a/src/features/Web3/components/TokenSelect/TokenSelect.tsx +++ b/src/features/Web3/components/TokenSelect/TokenSelect.tsx @@ -4,17 +4,21 @@ import { Token } from './Token' import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react' import { TokenLoading } from './TokenLoading' import { useEffect } from 'react' -import { useTokens } from '../../hooks/useTokens' import { useAccount, useNetwork } from 'wagmi' -import { $selectedToken, $setSelectedToken } from '../../stores/selectedToken' +import { useTokensStore } from '@features/Web3/hooks/useTokensStore' +import { $selectedToken, $setSelectedToken } from '@features/Web3/stores/tokens' import { useStore } from '@nanostores/react' export function TokenSelect() { - const { data: tokens, isLoading: tokensIsLoading } = useTokens() - const selectedToken = useStore($selectedToken) const { chain } = useNetwork() const { address } = useAccount() + const { data: tokens, loading } = useTokensStore({ + chainId: chain?.id, + address + }) + const selectedToken = useStore($selectedToken) + const items = tokens?.map((token) => ( )) @@ -28,7 +32,7 @@ export function TokenSelect() { // Set default token data to first item, // which most of time is native token useEffect(() => { - if (!chain?.id || !address || !tokens) return + if (!tokens) return if (!selectedToken || !selectedToken?.address) handleValueChange(tokens[0].address) @@ -38,15 +42,15 @@ export function TokenSelect() { handleValueChange(value)} - disabled={!tokens || tokensIsLoading} + disabled={!tokens || loading} value={selectedToken?.address} > - {tokensIsLoading ? : } + {loading ? : } diff --git a/src/features/Web3/hooks/useTokens/getTokens.ts b/src/features/Web3/hooks/useTokens/getTokens.ts deleted file mode 100644 index 24e79ef3..00000000 --- a/src/features/Web3/hooks/useTokens/getTokens.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { GetToken } from './types' - -export async function getTokens( - address: `0x${string}`, - chainId: number, - signal?: AbortSignal -): Promise { - if (!address || !chainId) return [] - - // const url = `http://localhost:3000/api/balance?address=${address}&chainId=${chainId}` - const url = `https://web3.kremalicious.com/api/balance?address=${address}&chainId=${chainId}` - const response = await fetch(url, { signal }) - const json: GetToken[] = await response.json() - - if (!json) console.error(response.statusText) - return json -} diff --git a/src/features/Web3/hooks/useTokens/index.tsx b/src/features/Web3/hooks/useTokens/index.tsx deleted file mode 100644 index e1d38f28..00000000 --- a/src/features/Web3/hooks/useTokens/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './useTokens' -export * from './types' diff --git a/src/features/Web3/hooks/useTokens/useTokens.tsx b/src/features/Web3/hooks/useTokens/useTokens.tsx deleted file mode 100644 index 616a0084..00000000 --- a/src/features/Web3/hooks/useTokens/useTokens.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useState, useEffect } from 'react' -import { useAccount, useNetwork } from 'wagmi' -import { getTokens } from './getTokens' -import type { GetToken } from './types' - -export function useTokens() { - const { address } = useAccount() - const { chain } = useNetwork() - - const [data, setData] = useState() - const [isLoading, setIsLoading] = useState() - const [isError, setIsError] = useState() - - useEffect(() => { - const abortController = new AbortController() - const { signal } = abortController - - async function init() { - if (!address || !chain?.id) return - - setIsLoading(true) - - try { - const tokens = await getTokens(address, chain.id, signal) - setData(tokens) - setIsLoading(false) - } catch (error: any) { - setIsError(true) - setIsLoading(false) - if ((error as Error).name !== 'AbortError') { - console.error((error as Error).message) - } - } - } - init() - - return () => { - abortController.abort() - setData(undefined) - setIsLoading(undefined) - setIsError(undefined) - } - }, [address, chain?.id]) - - return { data, isLoading, isError } -} diff --git a/src/features/Web3/hooks/useTokensStore.tsx b/src/features/Web3/hooks/useTokensStore.tsx new file mode 100644 index 00000000..c2efe9a2 --- /dev/null +++ b/src/features/Web3/hooks/useTokensStore.tsx @@ -0,0 +1,34 @@ +import { useStore } from '@nanostores/react' +import { useState } from 'react' +import { createTokensStore } from '../stores/tokens' + +// +// Wrapper around $tokens store. +// +// Workaround to dynamically create the store based on +// user address and chainId. +// +export function useTokensStore({ + address, + chainId +}: { + address: `0x${string}` | undefined + chainId: number | undefined +}) { + const store = createTokensStore(address, chainId) + + const [tokensFetchStore] = useState(store) + const $tokens = useStore(tokensFetchStore) + + // useEffect(() => { + // if (!chainId || !address) return + + // console.log(tokensFetchStore.key) + + // const newUrl = `https://web3.kremalicious.com/api/balance?address=${address}&chainId=${chainId}` + // tokensFetchStore.setKey(tokensFetchStore.key, newUrl) + // console.log(tokensFetchStore.key) + // }, [chainId, address]) + + return $tokens +} diff --git a/src/features/Web3/stores/tokens/fetcher.ts b/src/features/Web3/stores/tokens/fetcher.ts new file mode 100644 index 00000000..bff4d1fe --- /dev/null +++ b/src/features/Web3/stores/tokens/fetcher.ts @@ -0,0 +1,12 @@ +import { nanoquery } from '@nanostores/query' + +export const [ + createFetcherStore, + createMutatorStore, + { invalidateKeys, mutateCache } +] = nanoquery({ + fetcher: async (...keys: (string | number)[]) => { + const response = await fetch(keys.join('')) + return await response.json() + } +}) diff --git a/src/features/Web3/stores/tokens/index.ts b/src/features/Web3/stores/tokens/index.ts new file mode 100644 index 00000000..dcdf5c10 --- /dev/null +++ b/src/features/Web3/stores/tokens/index.ts @@ -0,0 +1,3 @@ +export * from './types' +export * from './tokens' +export * from './selectedToken' diff --git a/src/features/Web3/stores/selectedToken.ts b/src/features/Web3/stores/tokens/selectedToken.ts similarity index 72% rename from src/features/Web3/stores/selectedToken.ts rename to src/features/Web3/stores/tokens/selectedToken.ts index 9f6e173f..2e753579 100644 --- a/src/features/Web3/stores/selectedToken.ts +++ b/src/features/Web3/stores/tokens/selectedToken.ts @@ -1,10 +1,10 @@ import { action } from 'nanostores' import { persistentAtom } from '@nanostores/persistent' -import type { GetToken } from '../hooks/useTokens' +import type { GetToken } from './types' -export const $selectedToken = persistentAtom( +export const $selectedToken = persistentAtom( '@kremalicious/selectedToken', - { address: '0x0' } as any, + undefined, { encode: JSON.stringify, decode: JSON.parse diff --git a/src/features/Web3/stores/tokens/tokens.ts b/src/features/Web3/stores/tokens/tokens.ts new file mode 100644 index 00000000..2c11c673 --- /dev/null +++ b/src/features/Web3/stores/tokens/tokens.ts @@ -0,0 +1,12 @@ +import { createFetcherStore } from './fetcher' +import type { GetToken } from './types' + +export const createTokensStore = ( + address: `0x${string}` | undefined, + chainId: number | undefined +) => { + const url = `https://web3.kremalicious.com/api/balance?address=${address}&chainId=${chainId}` + const fetcherStore = createFetcherStore(url) + + return fetcherStore +} diff --git a/src/features/Web3/hooks/useTokens/types.ts b/src/features/Web3/stores/tokens/types.ts similarity index 100% rename from src/features/Web3/hooks/useTokens/types.ts rename to src/features/Web3/stores/tokens/types.ts