1
0
Fork 0

use nanostores

This commit is contained in:
Matthias Kretschmann 2023-10-30 19:37:33 +00:00
parent ecde440144
commit 21dfb2dd2a
Signed by: m
GPG Key ID: 606EEEF3C479A91F
17 changed files with 86 additions and 100 deletions

View File

@ -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(
<Alert
message={{ status: 'loading', text: 'Loading' }}

View File

@ -3,7 +3,7 @@ import { describe, it } from 'vitest'
import { Conversion } from './Conversion'
describe('Conversion', () => {
it('renders without crashing', async () => {
render(<Conversion amount="1" symbol="ETH" />)
it('renders without crashing', () => {
render(<Conversion amount="1" />)
})
})

View File

@ -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 {

View File

@ -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 {

View File

@ -6,14 +6,7 @@ const setAmount = vi.fn()
describe('InputGroup', () => {
it('renders without crashing', async () => {
render(
<InputGroup
amount="1"
setAmount={setAmount}
isDisabled={false}
symbol="ETH"
/>
)
render(<InputGroup amount="1" setAmount={setAmount} isDisabled={false} />)
const input = await screen.findByRole('textbox')
const button = await screen.findByRole('button')
@ -23,14 +16,7 @@ describe('InputGroup', () => {
})
it('renders disabled', async () => {
render(
<InputGroup
amount="1"
setAmount={setAmount}
isDisabled={true}
symbol="ETH"
/>
)
render(<InputGroup amount="1" setAmount={setAmount} isDisabled={true} />)
const input = await screen.findByRole('textbox')
expect(input).toBeDefined()

View File

@ -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: [

View File

@ -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<HTMLDivElement> {
token: GetToken | undefined

View File

@ -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) => (
<Token key={token.address} token={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() {
<Select.Root
defaultValue={selectedToken?.address}
onValueChange={(value: `0x${string}`) => handleValueChange(value)}
disabled={!tokens || tokensIsLoading}
disabled={!tokens || loading}
value={selectedToken?.address}
>
<Select.Trigger
className="SelectTrigger"
disabled={!tokens || tokensIsLoading}
disabled={!tokens || loading}
aria-label="Token"
>
{tokensIsLoading ? <TokenLoading /> : <Select.Value />}
{loading ? <TokenLoading /> : <Select.Value />}
<Select.Icon>
<ChevronDown />
</Select.Icon>

View File

@ -1,17 +0,0 @@
import type { GetToken } from './types'
export async function getTokens(
address: `0x${string}`,
chainId: number,
signal?: AbortSignal
): Promise<GetToken[]> {
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
}

View File

@ -1,2 +0,0 @@
export * from './useTokens'
export * from './types'

View File

@ -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<GetToken[]>()
const [isLoading, setIsLoading] = useState<boolean>()
const [isError, setIsError] = useState<boolean>()
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 }
}

View File

@ -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
}

View File

@ -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()
}
})

View File

@ -0,0 +1,3 @@
export * from './types'
export * from './tokens'
export * from './selectedToken'

View File

@ -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<GetToken>(
export const $selectedToken = persistentAtom<GetToken | undefined>(
'@kremalicious/selectedToken',
{ address: '0x0' } as any,
undefined,
{
encode: JSON.stringify,
decode: JSON.parse

View File

@ -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<GetToken[]>(url)
return fetcherStore
}