mirror of
https://github.com/kremalicious/blog.git
synced 2024-12-22 17:23:50 +01:00
switch to swr
This commit is contained in:
parent
acca82f6df
commit
c85c823812
36
package-lock.json
generated
36
package-lock.json
generated
@ -14,7 +14,6 @@
|
||||
"@astrojs/rss": "^3.0.0",
|
||||
"@astrojs/sitemap": "^3.0.2",
|
||||
"@coingecko/cryptoformat": "^0.6.0",
|
||||
"@nanostores/persistent": "^0.9.1",
|
||||
"@nanostores/query": "^0.2.4",
|
||||
"@nanostores/react": "^0.7.1",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
@ -34,6 +33,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"slugify": "^1.6.6",
|
||||
"swr": "^2.2.4",
|
||||
"use-debounce": "^9.0.4",
|
||||
"viem": "^1.18.0",
|
||||
"wagmi": "^1.4.5"
|
||||
@ -2016,23 +2016,6 @@
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@nanostores/persistent": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@nanostores/persistent/-/persistent-0.9.1.tgz",
|
||||
"integrity": "sha512-ow57Hxm5VMaI5GHET/cVk8hX/iKMmbhcGrB9owfN8p8OHiiJgUlYxe1giacwlAALJXAh2t8bxXh42hHb64BCEA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": "^16.0.0 || ^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"nanostores": "^0.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nanostores/query": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@nanostores/query/-/query-0.2.4.tgz",
|
||||
@ -6184,6 +6167,11 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
@ -17926,6 +17914,18 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/swr": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/swr/-/swr-2.2.4.tgz",
|
||||
"integrity": "sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==",
|
||||
"dependencies": {
|
||||
"client-only": "^0.0.1",
|
||||
"use-sync-external-store": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
|
@ -53,7 +53,6 @@
|
||||
"@astrojs/rss": "^3.0.0",
|
||||
"@astrojs/sitemap": "^3.0.2",
|
||||
"@coingecko/cryptoformat": "^0.6.0",
|
||||
"@nanostores/persistent": "^0.9.1",
|
||||
"@nanostores/query": "^0.2.4",
|
||||
"@nanostores/react": "^0.7.1",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
@ -73,6 +72,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"slugify": "^1.6.6",
|
||||
"swr": "^2.2.4",
|
||||
"use-debounce": "^9.0.4",
|
||||
"viem": "^1.18.0",
|
||||
"wagmi": "^1.4.5"
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useEffect, type ReactElement, useState } from 'react'
|
||||
import styles from './Conversion.module.css'
|
||||
import { $selectedToken } from '../../stores/tokens/selectedToken'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { useTokens } from '@features/Web3/hooks/useTokens'
|
||||
|
||||
export function Conversion({ amount }: { amount: string }): ReactElement {
|
||||
const selectedToken = useStore($selectedToken)
|
||||
const { selectedToken } = useTokens()
|
||||
|
||||
const [dollar, setDollar] = useState('0.00')
|
||||
const [euro, setEuro] = useState('0.00')
|
||||
@ -22,7 +21,7 @@ export function Conversion({ amount }: { amount: string }): ReactElement {
|
||||
const euro = eur ? (Number(amount) * eur).toFixed(2) : '0.00'
|
||||
setDollar(dollar)
|
||||
setEuro(euro)
|
||||
}, [selectedToken?.price, amount])
|
||||
}, [selectedToken, amount])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -8,12 +8,11 @@ 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/tokens/selectedToken'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { useTokens } from '@features/Web3/hooks/useTokens'
|
||||
|
||||
export default function Web3Form(): ReactElement {
|
||||
const { address: account } = useAccount()
|
||||
const selectedToken = useStore($selectedToken)
|
||||
const { selectedToken } = useTokens()
|
||||
|
||||
const [amount, setAmount] = useState('')
|
||||
const [debouncedAmount] = useDebounce(amount, 500)
|
||||
@ -37,7 +36,7 @@ export default function Web3Form(): ReactElement {
|
||||
<ConnectButton chainStatus="full" showBalance={false} />
|
||||
</div>
|
||||
|
||||
{message ? (
|
||||
{message && message.status !== 'error' ? (
|
||||
<Alert message={message} transactionHash={data?.hash} />
|
||||
) : (
|
||||
<InputGroup
|
||||
@ -47,6 +46,10 @@ export default function Web3Form(): ReactElement {
|
||||
/>
|
||||
)}
|
||||
|
||||
{message && message?.status === 'error' ? (
|
||||
<Alert message={message} />
|
||||
) : null}
|
||||
|
||||
{selectedToken?.address === '0x0' ? (
|
||||
<SendNative
|
||||
amount={debouncedAmount}
|
||||
|
@ -27,7 +27,6 @@ export function InputGroup({
|
||||
placeholder="0.00"
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
className={styles.inputInput}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<button
|
||||
className={`${styles.submit} btn btn-primary`}
|
||||
|
@ -3,8 +3,7 @@ import { useContractWrite, usePrepareContractWrite } from 'wagmi'
|
||||
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/tokens/selectedToken'
|
||||
import { useTokens } from '@features/Web3/hooks/useTokens'
|
||||
|
||||
export function SendErc20({
|
||||
amount,
|
||||
@ -13,7 +12,7 @@ export function SendErc20({
|
||||
amount: string
|
||||
setSendFormData: any
|
||||
}) {
|
||||
const selectedToken = useStore($selectedToken)
|
||||
const { selectedToken } = useTokens()
|
||||
|
||||
const { config } = usePrepareContractWrite({
|
||||
address: selectedToken?.address,
|
||||
|
@ -3,21 +3,15 @@ import './TokenSelect.css'
|
||||
import { Token } from './Token'
|
||||
import { ChevronDown, ChevronsDown, ChevronsUp } from '@images/components/react'
|
||||
import { TokenLoading } from './TokenLoading'
|
||||
import { useEffect } from 'react'
|
||||
import { useAccount, useNetwork } from 'wagmi'
|
||||
import { useTokensStore } from '@features/Web3/hooks/useTokensStore'
|
||||
import { $selectedToken, $setSelectedToken } from '@features/Web3/stores/tokens'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { useTokens } from '@features/Web3/hooks/useTokens'
|
||||
|
||||
export function TokenSelect() {
|
||||
const { chain } = useNetwork()
|
||||
const { address } = useAccount()
|
||||
|
||||
const { data: tokens, loading } = useTokensStore({
|
||||
chainId: chain?.id,
|
||||
address
|
||||
})
|
||||
const selectedToken = useStore($selectedToken)
|
||||
const {
|
||||
data: tokens,
|
||||
isLoading,
|
||||
selectedToken,
|
||||
setSelectedToken
|
||||
} = useTokens()
|
||||
|
||||
const items = tokens?.map((token) => (
|
||||
<Token key={token.address} token={token} />
|
||||
@ -26,31 +20,22 @@ export function TokenSelect() {
|
||||
function handleValueChange(value: `0x${string}`) {
|
||||
const token = tokens?.find((token) => token.address === value)
|
||||
if (!token) return
|
||||
$setSelectedToken(token)
|
||||
setSelectedToken(token)
|
||||
}
|
||||
|
||||
// Set default token data to first item,
|
||||
// which most of time is native token
|
||||
useEffect(() => {
|
||||
if (!tokens) return
|
||||
|
||||
if (!selectedToken || !selectedToken?.address)
|
||||
handleValueChange(tokens[0].address)
|
||||
}, [chain?.id, address, tokens, selectedToken])
|
||||
|
||||
return (
|
||||
<Select.Root
|
||||
defaultValue={selectedToken?.address}
|
||||
onValueChange={(value: `0x${string}`) => handleValueChange(value)}
|
||||
disabled={!tokens || loading}
|
||||
disabled={!tokens || isLoading}
|
||||
value={selectedToken?.address}
|
||||
>
|
||||
<Select.Trigger
|
||||
className="SelectTrigger"
|
||||
disabled={!tokens || loading}
|
||||
disabled={!tokens || isLoading}
|
||||
aria-label="Token"
|
||||
>
|
||||
{loading ? <TokenLoading /> : <Select.Value />}
|
||||
{isLoading ? <TokenLoading /> : <Select.Value />}
|
||||
<Select.Icon>
|
||||
<ChevronDown />
|
||||
</Select.Icon>
|
||||
|
2
src/features/Web3/hooks/useTokens/index.ts
Normal file
2
src/features/Web3/hooks/useTokens/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './useTokens'
|
||||
export * from './types'
|
39
src/features/Web3/hooks/useTokens/useTokens.tsx
Normal file
39
src/features/Web3/hooks/useTokens/useTokens.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import type { GetToken } from './types'
|
||||
import { useNetwork, useAccount } from 'wagmi'
|
||||
import { fetcher } from '@stores/fetcher'
|
||||
|
||||
//
|
||||
// Wrapper for fetching user tokens with swr,
|
||||
// and simple store for selected token.
|
||||
//
|
||||
export function useTokens() {
|
||||
const { chain } = useNetwork()
|
||||
const { address } = useAccount()
|
||||
|
||||
const [url, setUrl] = useState<string | undefined>(undefined)
|
||||
const [selectedToken, setSelectedToken] = useState<GetToken | undefined>()
|
||||
|
||||
const fetchResults = useSWR<GetToken[] | undefined>(url, fetcher)
|
||||
const { data: tokens } = fetchResults
|
||||
|
||||
// Set url only after we have all data loaded on client,
|
||||
// preventing initial fetch.
|
||||
useEffect(() => {
|
||||
if (!address || !chain?.id) return
|
||||
|
||||
const url = `https://web3.kremalicious.com/api/balance?address=${address}&chainId=${chain?.id}`
|
||||
setUrl(url)
|
||||
}, [address, chain?.id])
|
||||
|
||||
// Set default token data to first item,
|
||||
// which most of time is native token
|
||||
useEffect(() => {
|
||||
if (!tokens?.[0]?.address) return
|
||||
|
||||
setSelectedToken(tokens?.[0])
|
||||
}, [tokens?.[0]?.address])
|
||||
|
||||
return { ...fetchResults, selectedToken, setSelectedToken }
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
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()
|
||||
}
|
||||
})
|
@ -1,3 +0,0 @@
|
||||
export * from './types'
|
||||
export * from './tokens'
|
||||
export * from './selectedToken'
|
@ -1,21 +0,0 @@
|
||||
import { action } from 'nanostores'
|
||||
import { persistentAtom } from '@nanostores/persistent'
|
||||
import type { GetToken } from './types'
|
||||
|
||||
export const $selectedToken = persistentAtom<GetToken | undefined>(
|
||||
'@kremalicious/selectedToken',
|
||||
undefined,
|
||||
{
|
||||
encode: JSON.stringify,
|
||||
decode: JSON.parse
|
||||
}
|
||||
)
|
||||
|
||||
export const $setSelectedToken = action(
|
||||
$selectedToken,
|
||||
'setSelectedToken',
|
||||
(store, token: GetToken) => {
|
||||
store.set(token)
|
||||
return store.get()
|
||||
}
|
||||
)
|
@ -1,12 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
import { nanoquery } from '@nanostores/query'
|
||||
|
||||
export async function fetcher(...args: (string | number)[]) {
|
||||
const res = await fetch(args.join(''))
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
export const [createFetcherStore, createMutatorStore] = nanoquery({
|
||||
fetcher: (...keys: (string | number)[]) =>
|
||||
fetch(keys.join('')).then((r) => r.json())
|
||||
fetcher
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user