Introduce redux for one of the state stores (#742)

* Introduce redux for one of the state stores

* Remove unused state

* Clean up hooks

* Add types for react-redux and fix error from any type on useSelector

* Strongly type the web3 provider

* Make the popup content into a POJO

* Lint errors

* Clean up method call

* Fix lint error

* Fix lint error

* Lint
This commit is contained in:
Moody Salem 2020-05-11 18:23:01 -04:00 committed by GitHub
parent ea015d16f2
commit 6da8e2c84d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 355 additions and 293 deletions

3
.gitignore vendored

@ -30,4 +30,5 @@ notes.txt
package-lock.json package-lock.json
cypress/videos cypress/videos
cypress/screenshots cypress/screenshots
cypress/fixtures/example.json

@ -15,6 +15,7 @@
"@material-ui/core": "^4.9.5", "@material-ui/core": "^4.9.5",
"@reach/dialog": "^0.2.8", "@reach/dialog": "^0.2.8",
"@reach/tooltip": "^0.2.0", "@reach/tooltip": "^0.2.0",
"@reduxjs/toolkit": "^1.3.5",
"@uniswap/sdk": "^2.0.3", "@uniswap/sdk": "^2.0.3",
"@uniswap/v2-core": "1.0.0", "@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "1.0.0-beta.0", "@uniswap/v2-periphery": "1.0.0-beta.0",
@ -41,6 +42,7 @@
"react-feather": "^2.0.8", "react-feather": "^2.0.8",
"react-ga": "^2.5.7", "react-ga": "^2.5.7",
"react-i18next": "^10.7.0", "react-i18next": "^10.7.0",
"react-redux": "^7.2.0",
"react-router-dom": "^5.0.0", "react-router-dom": "^5.0.0",
"react-scripts": "^3.4.1", "react-scripts": "^3.4.1",
"react-spring": "^8.0.27", "react-spring": "^8.0.27",
@ -55,6 +57,7 @@
"@types/node": "^13.13.5", "@types/node": "^13.13.5",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"@types/react-dom": "^16.9.7", "@types/react-dom": "^16.9.7",
"@types/react-redux": "^7.1.8",
"@types/react-router-dom": "^5.0.0", "@types/react-router-dom": "^5.0.0",
"@types/rebass": "^4.0.5", "@types/rebass": "^4.0.5",
"@types/styled-components": "^4.2.0", "@types/styled-components": "^4.2.0",

@ -7,6 +7,7 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { MaxUint256 } from '@ethersproject/constants' import { MaxUint256 } from '@ethersproject/constants'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { useUserAdvanced } from '../../state/application/hooks'
import { Field, SwapAction, useSwapStateReducer } from './swap-store' import { Field, SwapAction, useSwapStateReducer } from './swap-store'
import { Text } from 'rebass' import { Text } from 'rebass'
import Card, { BlueCard, GreyCard, YellowCard } from '../../components/Card' import Card, { BlueCard, GreyCard, YellowCard } from '../../components/Card'
@ -14,7 +15,6 @@ import { AutoColumn, ColumnCenter } from '../../components/Column'
import { AutoRow, RowBetween, RowFixed } from '../Row' import { AutoRow, RowBetween, RowFixed } from '../Row'
import { ROUTER_ADDRESS } from '../../constants' import { ROUTER_ADDRESS } from '../../constants'
import { useTokenAllowance } from '../../data/Allowances' import { useTokenAllowance } from '../../data/Allowances'
import { useUserAdvanced } from '../../contexts/Application'
import { useAddressBalance, useAllBalances } from '../../contexts/Balances' import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
import { useLocalStorageTokens } from '../../contexts/LocalStorage' import { useLocalStorageTokens } from '../../contexts/LocalStorage'
import { usePair } from '../../data/Reserves' import { usePair } from '../../data/Reserves'

@ -3,8 +3,14 @@ import styled, { ThemeContext } from 'styled-components'
import { useMediaLayout } from 'use-media' import { useMediaLayout } from 'use-media'
import { X } from 'react-feather' import { X } from 'react-feather'
import { usePopups } from '../../contexts/Application' import { PopupContent } from '../../state/application/actions'
import { usePopups } from '../../state/application/hooks'
import { Link } from '../../theme'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import DoubleTokenLogo from '../DoubleLogo'
import Row from '../Row'
import TxnPopup from '../TxnPopup'
import { Text } from 'rebass'
const StyledClose = styled(X)` const StyledClose = styled(X)`
position: absolute; position: absolute;
@ -66,6 +72,33 @@ const Popup = styled.div`
`} `}
` `
function PopupItem({ content, popKey }: { content: PopupContent; popKey: string }) {
if ('txn' in content) {
const {
txn: { hash, success, summary }
} = content
return <TxnPopup popKey={popKey} hash={hash} success={success} summary={summary} />
} else if ('poolAdded' in content) {
const {
poolAdded: { token0, token1 }
} = content
return (
<AutoColumn gap={'10px'}>
<Text fontSize={20} fontWeight={500}>
Pool Imported
</Text>
<Row>
<DoubleTokenLogo a0={token0?.address ?? ''} a1={token1?.address ?? ''} margin={true} />
<Text fontSize={16} fontWeight={500}>
UNI {token0?.symbol} / {token1?.symbol}
</Text>
</Row>
<Link>View on Uniswap Info.</Link>
</AutoColumn>
)
}
}
export default function App() { export default function App() {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
// get all popups // get all popups
@ -81,7 +114,7 @@ export default function App() {
return ( return (
<Popup key={item.key}> <Popup key={item.key}>
<StyledClose color={theme.text2} onClick={() => removePopup(item.key)} /> <StyledClose color={theme.text2} onClick={() => removePopup(item.key)} />
{React.cloneElement(item.content, { popKey: item.key })} <PopupItem content={item.content} popKey={item.key} />
</Popup> </Popup>
) )
})} })}
@ -100,7 +133,7 @@ export default function App() {
return ( return (
<Popup key={item.key}> <Popup key={item.key}>
<StyledClose color={theme.text2} onClick={() => removePopup(item.key)} /> <StyledClose color={theme.text2} onClick={() => removePopup(item.key)} />
{React.cloneElement(item.content, { popKey: item.key })} <PopupItem content={item.content} popKey={item.key} />
</Popup> </Popup>
) )
})} })}

@ -3,10 +3,10 @@ import React, { useState } from 'react'
import { AlertCircle, CheckCircle } from 'react-feather' import { AlertCircle, CheckCircle } from 'react-feather'
import styled from 'styled-components' import styled from 'styled-components'
import { usePopups } from '../../contexts/Application'
import { useWeb3React } from '../../hooks' import { useWeb3React } from '../../hooks'
import useInterval from '../../hooks/useInterval' import useInterval from '../../hooks/useInterval'
import { usePopups } from '../../state/application/hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { Link } from '../../theme/components' import { Link } from '../../theme/components'
@ -35,7 +35,7 @@ export default function TxnPopup({
hash: string hash: string
success?: boolean success?: boolean
summary?: string summary?: string
popKey?: number popKey?: string
}) { }) {
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const [count, setCount] = useState(1) const [count, setCount] = useState(1)

@ -4,6 +4,7 @@ import styled from 'styled-components'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core' import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'
import { URI_AVAILABLE } from '@web3-react/walletconnect-connector' import { URI_AVAILABLE } from '@web3-react/walletconnect-connector'
import { useWalletModalOpen, useWalletModalToggle } from '../../state/application/hooks'
import Modal from '../Modal' import Modal from '../Modal'
import AccountDetails from '../AccountDetails' import AccountDetails from '../AccountDetails'
@ -15,7 +16,6 @@ import { Link } from '../../theme'
import MetamaskIcon from '../../assets/images/metamask.png' import MetamaskIcon from '../../assets/images/metamask.png'
import { ReactComponent as Close } from '../../assets/images/x.svg' import { ReactComponent as Close } from '../../assets/images/x.svg'
import { injected, walletconnect, fortmatic, portis } from '../../connectors' import { injected, walletconnect, fortmatic, portis } from '../../connectors'
import { useWalletModalToggle, useWalletModalOpen } from '../../contexts/Application'
import { OVERLAY_READY } from '../../connectors/Fortmatic' import { OVERLAY_READY } from '../../connectors/Fortmatic'
const CloseIcon = styled.div` const CloseIcon = styled.div`

@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core' import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'
import { darken, lighten } from 'polished' import { darken, lighten } from 'polished'
import { Activity } from 'react-feather' import { Activity } from 'react-feather'
import { useWalletModalToggle } from '../../state/application/hooks'
import Identicon from '../Identicon' import Identicon from '../Identicon'
import PortisIcon from '../../assets/images/portisIcon.png' import PortisIcon from '../../assets/images/portisIcon.png'
@ -21,7 +22,6 @@ import { useENSName } from '../../hooks'
import { shortenAddress } from '../../utils' import { shortenAddress } from '../../utils'
import { useAllTransactions } from '../../contexts/Transactions' import { useAllTransactions } from '../../contexts/Transactions'
import { NetworkContextName } from '../../constants' import { NetworkContextName } from '../../constants'
import { useWalletModalToggle } from '../../contexts/Application'
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors' import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
const SpinnerWrapper = styled(Spinner)` const SpinnerWrapper = styled(Spinner)`

@ -1,226 +0,0 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3React } from '../hooks'
const BLOCK_NUMBER = 'BLOCK_NUMBER'
const USD_PRICE = 'USD_PRICE'
const WALLET_MODAL_OPEN = 'WALLET_MODAL_OPEN'
const POPUP_LIST = 'POPUP_LIST'
const POPUP_KEY = 'POPUP_KEY'
const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER'
const TOGGLE_WALLET_MODAL = 'TOGGLE_WALLET_MODAL'
const ADD_POPUP = 'ADD_POPUP'
const USER_ADVANCED = 'USER_ADVANCED'
const TOGGLE_USER_ADVANCED = 'TOGGLE_USER_ADVANCED'
interface ApplicationState {
BLOCK_NUMBER: {}
USD_PRICE: {}
POPUP_LIST: Array<{ key: number; show: boolean; content: React.ReactElement }>
POPUP_KEY: number
WALLET_MODAL_OPEN: boolean
USER_ADVANCED: boolean
}
const ApplicationContext = createContext<[ApplicationState, { [updater: string]: (...args: any[]) => void }]>([
{
[BLOCK_NUMBER]: {},
[USD_PRICE]: {},
[POPUP_LIST]: [],
[POPUP_KEY]: 0,
[WALLET_MODAL_OPEN]: false,
[USER_ADVANCED]: false
},
{}
])
function useApplicationContext() {
return useContext(ApplicationContext)
}
function reducer(state: ApplicationState, { type, payload }): ApplicationState {
switch (type) {
case UPDATE_BLOCK_NUMBER: {
const { networkId, blockNumber } = payload
return {
...state,
[BLOCK_NUMBER]: {
...state?.[BLOCK_NUMBER],
[networkId]: blockNumber
}
}
}
case TOGGLE_WALLET_MODAL: {
return { ...state, [WALLET_MODAL_OPEN]: !state[WALLET_MODAL_OPEN] }
}
case TOGGLE_USER_ADVANCED: {
return { ...state, [USER_ADVANCED]: !state[USER_ADVANCED] }
}
case ADD_POPUP: {
const { newList } = payload
return { ...state, [POPUP_LIST]: newList, [POPUP_KEY]: state?.[POPUP_KEY] + 1 }
}
default: {
throw Error(`Unexpected action type in ApplicationContext reducer: '${type}'.`)
}
}
}
export default function Provider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, {
[BLOCK_NUMBER]: {},
[USD_PRICE]: {},
[POPUP_LIST]: [],
[POPUP_KEY]: 0,
[WALLET_MODAL_OPEN]: false,
[USER_ADVANCED]: false
})
const updateBlockNumber = useCallback(
(networkId, blockNumber) => {
dispatch({ type: UPDATE_BLOCK_NUMBER, payload: { networkId, blockNumber } })
},
[dispatch]
)
const toggleWalletModal = useCallback(() => {
dispatch({ type: TOGGLE_WALLET_MODAL, payload: null })
}, [dispatch])
const toggleUserAdvanced = useCallback(() => {
dispatch({ type: TOGGLE_USER_ADVANCED, payload: null })
}, [dispatch])
const setPopups = useCallback(
newList => {
dispatch({ type: ADD_POPUP, payload: { newList } })
},
[dispatch]
)
return (
<ApplicationContext.Provider
value={useMemo(() => [state, { updateBlockNumber, toggleWalletModal, toggleUserAdvanced, setPopups }], [
state,
updateBlockNumber,
toggleWalletModal,
toggleUserAdvanced,
setPopups
])}
>
{children}
</ApplicationContext.Provider>
)
}
export function Updater() {
const { library, chainId } = useWeb3React()
const [, { updateBlockNumber }] = useApplicationContext()
// update block number
useEffect(() => {
if (library) {
let stale = false
const update = () => {
library
.getBlockNumber()
.then(blockNumber => {
if (!stale) {
updateBlockNumber(chainId, blockNumber)
}
})
.catch(() => {
if (!stale) {
updateBlockNumber(chainId, null)
}
})
}
update()
library.on('block', update)
return () => {
stale = true
library.removeListener('block', update)
}
}
}, [chainId, library, updateBlockNumber])
return null
}
export function useBlockNumber() {
const { chainId } = useWeb3React()
const [state] = useApplicationContext()
return state?.[BLOCK_NUMBER]?.[chainId]
}
export function useWalletModalOpen() {
const [state] = useApplicationContext()
return state[WALLET_MODAL_OPEN]
}
export function useWalletModalToggle() {
const [, { toggleWalletModal }] = useApplicationContext()
return toggleWalletModal
}
export function useUserAdvanced() {
const [state] = useApplicationContext()
return state[USER_ADVANCED]
}
export function useToggleUserAdvanced() {
const [, { toggleUserAdvanced }] = useApplicationContext()
return toggleUserAdvanced
}
export function usePopups(): [
ApplicationState['POPUP_LIST'],
(content: React.ReactElement) => void,
(key: number) => void
] {
const [state, { setPopups }] = useApplicationContext()
const index = state[POPUP_KEY]
const currentPopups = state[POPUP_LIST]
function addPopup(content: React.ReactElement): void {
const newItem = {
show: true,
key: index,
content: content
}
currentPopups.push(newItem)
setPopups(currentPopups)
}
function removePopup(key: number): void {
currentPopups.map(item => {
if (key === item.key) {
item.show = false
}
return true
})
setPopups(currentPopups)
}
const activePopups = currentPopups.filter(item => {
return item.show
})
return [activePopups, addPopup, removePopup]
}

@ -1,8 +1,8 @@
import React, { createContext, useContext, useReducer, useRef, useMemo, useCallback, useEffect, ReactNode } from 'react' import React, { createContext, useContext, useReducer, useRef, useMemo, useCallback, useEffect, ReactNode } from 'react'
import { TokenAmount, Token, JSBI, WETH } from '@uniswap/sdk' import { TokenAmount, Token, JSBI, WETH } from '@uniswap/sdk'
import { useBlockNumber } from '../state/application/hooks'
import { useAllTokens } from './Tokens' import { useAllTokens } from './Tokens'
import { useBlockNumber } from './Application'
import { useWeb3React, useDebounce } from '../hooks' import { useWeb3React, useDebounce } from '../hooks'
import { getEtherBalance, getTokenBalance, isAddress } from '../utils' import { getEtherBalance, getTokenBalance, isAddress } from '../utils'
@ -158,28 +158,46 @@ function useBalancesContext() {
export default function Provider({ children }: { children: ReactNode }) { export default function Provider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, undefined, initialize) const [state, dispatch] = useReducer(reducer, undefined, initialize)
const startListening = useCallback((chainId, address, tokenAddress) => { const startListening = useCallback(
dispatch({ type: Action.START_LISTENING, payload: { chainId, address, tokenAddress } }) (chainId, address, tokenAddress) => {
}, []) dispatch({ type: Action.START_LISTENING, payload: { chainId, address, tokenAddress } })
},
[dispatch]
)
const stopListening = useCallback((chainId, address, tokenAddress) => { const stopListening = useCallback(
dispatch({ type: Action.STOP_LISTENING, payload: { chainId, address, tokenAddress } }) (chainId, address, tokenAddress) => {
}, []) dispatch({ type: Action.STOP_LISTENING, payload: { chainId, address, tokenAddress } })
},
[dispatch]
)
const update = useCallback((chainId, address, tokenAddress, value, blockNumber) => { const update = useCallback(
dispatch({ type: Action.UPDATE, payload: { chainId, address, tokenAddress, value, blockNumber } }) (chainId, address, tokenAddress, value, blockNumber) => {
}, []) dispatch({ type: Action.UPDATE, payload: { chainId, address, tokenAddress, value, blockNumber } })
},
[dispatch]
)
const batchUpdateAccount = useCallback((chainId, address, tokenAddresses, values, blockNumber) => { const batchUpdateAccount = useCallback(
dispatch({ type: Action.BATCH_UPDATE_ACCOUNT, payload: { chainId, address, tokenAddresses, values, blockNumber } }) (chainId, address, tokenAddresses, values, blockNumber) => {
}, []) dispatch({
type: Action.BATCH_UPDATE_ACCOUNT,
payload: { chainId, address, tokenAddresses, values, blockNumber }
})
},
[dispatch]
)
const batchUpdateExchanges = useCallback((chainId, pairAddresses, tokenAddresses, values, blockNumber) => { const batchUpdateExchanges = useCallback(
dispatch({ (chainId, pairAddresses, tokenAddresses, values, blockNumber) => {
type: Action.BATCH_UPDATE_EXCHANGES, dispatch({
payload: { chainId, pairAddresses, tokenAddresses, values, blockNumber } type: Action.BATCH_UPDATE_EXCHANGES,
}) payload: { chainId, pairAddresses, tokenAddresses, values, blockNumber }
}, []) })
},
[dispatch]
)
return ( return (
<BalancesContext.Provider <BalancesContext.Provider

@ -1,9 +1,7 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import TxnPopup from '../components/TxnPopup'
import { useWeb3React } from '../hooks' import { useWeb3React } from '../hooks'
import { useBlockNumber, usePopups } from './Application' import { useBlockNumber, usePopups } from '../state/application/hooks'
const ADD = 'ADD' const ADD = 'ADD'
const CHECK = 'CHECK' const CHECK = 'CHECK'
@ -140,23 +138,17 @@ export function Updater() {
finalize(chainId, hash, receipt) finalize(chainId, hash, receipt)
// add success or failure popup // add success or failure popup
if (receipt.status === 1) { if (receipt.status === 1) {
addPopup( addPopup({
<TxnPopup txn: {
popKey={1} hash,
hash={hash} success: true,
success={true} summary: allTransactions[hash]?.response?.summary
summary={allTransactions[hash]?.response?.summary} }
/> })
)
} else { } else {
addPopup( addPopup({
<TxnPopup txn: { hash, success: false, summary: allTransactions[hash]?.response?.summary }
popKey={2} })
hash={hash}
success={false}
summary={allTransactions[hash]?.response?.summary}
/>
)
} }
} }
} }

@ -1,3 +1,4 @@
import { Web3Provider } from '@ethersproject/providers'
import { useState, useMemo, useCallback, useEffect, useRef } from 'react' import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
import { useWeb3React as useWeb3ReactCore } from '@web3-react/core' import { useWeb3React as useWeb3ReactCore } from '@web3-react/core'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
@ -9,8 +10,8 @@ import { NetworkContextName } from '../constants'
import { getContract, getExchangeContract, isAddress } from '../utils' import { getContract, getExchangeContract, isAddress } from '../utils'
export function useWeb3React() { export function useWeb3React() {
const context = useWeb3ReactCore() const context = useWeb3ReactCore<Web3Provider>()
const contextNetwork = useWeb3ReactCore(NetworkContextName) const contextNetwork = useWeb3ReactCore<Web3Provider>(NetworkContextName)
return context.active ? context : contextNetwork return context.active ? context : contextNetwork
} }

@ -3,14 +3,16 @@ import ReactDOM from 'react-dom'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { Web3ReactProvider, createWeb3ReactRoot } from '@web3-react/core' import { Web3ReactProvider, createWeb3ReactRoot } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers' import { Web3Provider } from '@ethersproject/providers'
import { Provider } from 'react-redux'
import { NetworkContextName } from './constants' import { NetworkContextName } from './constants'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage' import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage'
import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application'
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions' import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances' import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances'
import App from './pages/App' import App from './pages/App'
import store from './state'
import { Updater as ApplicationContextUpdater } from './state/application/updater'
import ThemeProvider, { FixedGlobalStyle, ThemedGlobalStyle } from './theme' import ThemeProvider, { FixedGlobalStyle, ThemedGlobalStyle } from './theme'
import './i18n' import './i18n'
@ -36,11 +38,9 @@ ReactGA.pageview(window.location.pathname + window.location.search)
function ContextProviders({ children }: { children: React.ReactNode }) { function ContextProviders({ children }: { children: React.ReactNode }) {
return ( return (
<LocalStorageContextProvider> <LocalStorageContextProvider>
<ApplicationContextProvider> <TransactionContextProvider>
<TransactionContextProvider> <BalancesContextProvider>{children}</BalancesContextProvider>
<BalancesContextProvider>{children}</BalancesContextProvider> </TransactionContextProvider>
</TransactionContextProvider>
</ApplicationContextProvider>
</LocalStorageContextProvider> </LocalStorageContextProvider>
) )
} }
@ -61,15 +61,17 @@ ReactDOM.render(
<FixedGlobalStyle /> <FixedGlobalStyle />
<Web3ReactProvider getLibrary={getLibrary}> <Web3ReactProvider getLibrary={getLibrary}>
<Web3ProviderNetwork getLibrary={getLibrary}> <Web3ProviderNetwork getLibrary={getLibrary}>
<ContextProviders> <Provider store={store}>
<Updaters /> <ContextProviders>
<ThemeProvider> <Updaters />
<> <ThemeProvider>
<ThemedGlobalStyle /> <>
<App /> <ThemedGlobalStyle />
</> <App />
</ThemeProvider> </>
</ContextProviders> </ThemeProvider>
</ContextProviders>
</Provider>
</Web3ProviderNetwork> </Web3ProviderNetwork>
</Web3ReactProvider> </Web3ReactProvider>
</>, </>,

@ -0,0 +1,28 @@
import { createAction } from '@reduxjs/toolkit'
export type PopupContent =
| {
txn: {
hash: string
success?: boolean
summary?: string
}
}
| {
poolAdded: {
token0?: {
address?: string
symbol?: string
}
token1: {
address?: string
symbol?: string
}
}
}
export const updateBlockNumber = createAction<{ networkId: number; blockNumber: number | null }>('updateBlockNumber')
export const toggleWalletModal = createAction<void>('toggleWalletModal')
export const toggleUserAdvanced = createAction<void>('toggleUserAdvanced')
export const addPopup = createAction<{ content: PopupContent }>('addPopup')
export const removePopup = createAction<{ key: string }>('removePopup')

@ -0,0 +1,49 @@
import { useCallback } from 'react'
import { useWeb3React } from '../../hooks'
import { addPopup, PopupContent, removePopup, toggleWalletModal } from './actions'
import { useSelector, useDispatch } from 'react-redux'
import { AppState } from '../index'
export function useBlockNumber() {
const { chainId } = useWeb3React()
return useSelector((state: AppState) => state.application.blockNumber[chainId])
}
export function useWalletModalOpen() {
return useSelector((state: AppState) => state.application.walletModalOpen)
}
export function useWalletModalToggle() {
const dispatch = useDispatch()
return useCallback(() => dispatch(toggleWalletModal()), [dispatch])
}
export function useUserAdvanced() {
return useSelector((state: AppState) => state.application.userAdvanced)
}
export function usePopups(): [
AppState['application']['popupList'],
(content: PopupContent) => void,
(key: string) => void
] {
const dispatch = useDispatch()
const activePopups = useSelector((state: AppState) => state.application.popupList.filter(item => item.show))
const wrappedAddPopup = useCallback(
(content: PopupContent) => {
dispatch(addPopup({ content }))
},
[dispatch]
)
const wrappedRemovePopup = useCallback(
(key: string) => {
dispatch(removePopup({ key }))
},
[dispatch]
)
return [activePopups, wrappedAddPopup, wrappedRemovePopup]
}

@ -0,0 +1,53 @@
import { createReducer, nanoid } from '@reduxjs/toolkit'
import {
addPopup,
PopupContent,
removePopup,
toggleUserAdvanced,
toggleWalletModal,
updateBlockNumber
} from './actions'
type PopupList = Array<{ key: string; show: boolean; content: PopupContent }>
interface ApplicationState {
blockNumber: { [chainId: number]: number }
popupList: PopupList
walletModalOpen: boolean
userAdvanced: boolean
}
const initialState: ApplicationState = {
blockNumber: {},
popupList: [],
walletModalOpen: false,
userAdvanced: false
}
export default createReducer(initialState, builder =>
builder
.addCase(updateBlockNumber, (state, action) => {
const { networkId, blockNumber } = action.payload
state.blockNumber[networkId] = blockNumber
})
.addCase(toggleUserAdvanced, state => {
state.userAdvanced = !state.userAdvanced
})
.addCase(toggleWalletModal, state => {
state.walletModalOpen = !state.walletModalOpen
})
.addCase(addPopup, (state, { payload: { content } }) => {
state.popupList.push({
key: nanoid(),
show: true,
content
})
})
.addCase(removePopup, (state, { payload: { key } }) => {
state.popupList.forEach(p => {
if (p.key === key) {
p.show = false
}
})
})
)

@ -0,0 +1,41 @@
import { useEffect } from 'react'
import { useWeb3React } from '../../hooks'
import { updateBlockNumber } from './actions'
import { useDispatch } from 'react-redux'
export function Updater() {
const { library, chainId } = useWeb3React()
const dispatch = useDispatch()
// update block number
useEffect(() => {
if (library) {
let stale = false
const update = () => {
library
.getBlockNumber()
.then(blockNumber => {
if (!stale) {
dispatch(updateBlockNumber({ networkId: chainId, blockNumber }))
}
})
.catch(() => {
if (!stale) {
dispatch(updateBlockNumber({ networkId: chainId, blockNumber: null }))
}
})
}
update()
library.on('block', update)
return () => {
stale = true
library.removeListener('block', update)
}
}
}, [dispatch, chainId, library])
return null
}

13
src/state/index.ts Normal file

@ -0,0 +1,13 @@
import { configureStore } from '@reduxjs/toolkit'
import application from './application/reducer'
const store = configureStore({
reducer: {
application
}
})
export default store
export type AppState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

@ -2074,6 +2074,16 @@
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.1.4.tgz#0dc4ecedf523004337214187db70a46183bd945b" resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.1.4.tgz#0dc4ecedf523004337214187db70a46183bd945b"
integrity sha512-QHbzXjflSlCvDd6vJwdwx16mSB+vUCCQMiU/wK/CgVNPibtpEiIbisyxkpZc55DyDFNUIqP91rSUsNae+ogGDQ== integrity sha512-QHbzXjflSlCvDd6vJwdwx16mSB+vUCCQMiU/wK/CgVNPibtpEiIbisyxkpZc55DyDFNUIqP91rSUsNae+ogGDQ==
"@reduxjs/toolkit@^1.3.5":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.3.5.tgz#37c1ab6de9aa66f95bab25a8e9bd9d8ec3b7b80c"
integrity sha512-QVqI2T6kwT/3CVdCa6KjUDdEz9YY1eCLQVcRZamiaOAcKI7kkJSh2P0GjaaKXTjIFy0u9sWCXjzifPJNGoXjlw==
dependencies:
immer "^6.0.1"
redux "^4.0.0"
redux-thunk "^2.3.0"
reselect "^4.0.0"
"@samverschueren/stream-to-observable@^0.3.0": "@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@ -2413,7 +2423,7 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860"
integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw==
"@types/hoist-non-react-statics@*": "@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0":
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
@ -2527,6 +2537,16 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-redux@^7.1.8":
version "7.1.8"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.8.tgz#3631feb559f7858d6ad9eea1d6ef41fa64fe7205"
integrity sha512-kpplH7Wg2SYU00sZVT98WBN0ou6QKrYcShRaW+5Vpe5l7bluKWJbWmAL+ieiso07OQzpcP5i1PeY3690640ZWg==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react-router-dom@^5.0.0": "@types/react-router-dom@^5.0.0":
version "5.1.5" version "5.1.5"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090"
@ -9216,6 +9236,11 @@ immer@1.10.0:
resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==
immer@^6.0.1:
version "6.0.5"
resolved "https://registry.yarnpkg.com/immer/-/immer-6.0.5.tgz#77187d13b71c6cee40dde3b8e87a50a7a636d630"
integrity sha512-Q2wd90qrgFieIpLzAO2q9NLEdmyp/sr76Ml4Vm5peUKgyTa2CQa3ey8zuzwSKOlKH7grCeGBGUcLLVCVW1aguA==
import-cwd@^2.0.0: import-cwd@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
@ -13993,11 +14018,22 @@ react-i18next@^10.7.0:
"@babel/runtime" "^7.3.1" "@babel/runtime" "^7.3.1"
html-parse-stringify2 "2.0.1" html-parse-stringify2 "2.0.1"
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4: react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-redux@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==
dependencies:
"@babel/runtime" "^7.5.5"
hoist-non-react-statics "^3.3.0"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^16.9.0"
react-remove-scroll-bar@^1.1.5: react-remove-scroll-bar@^1.1.5:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-1.2.0.tgz#07250b2bc581f56315759c454c9b159dd04ba49d" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-1.2.0.tgz#07250b2bc581f56315759c454c9b159dd04ba49d"
@ -14280,6 +14316,19 @@ recursive-readdir@2.2.2:
dependencies: dependencies:
minimatch "3.0.4" minimatch "3.0.4"
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux@^4.0.0:
version "4.0.5"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
dependencies:
loose-envify "^1.4.0"
symbol-observable "^1.2.0"
reflexbox@^4.0.6: reflexbox@^4.0.6:
version "4.0.6" version "4.0.6"
resolved "https://registry.yarnpkg.com/reflexbox/-/reflexbox-4.0.6.tgz#fc756d2cc1ca493baf9b96bb27dd640ad8154cf1" resolved "https://registry.yarnpkg.com/reflexbox/-/reflexbox-4.0.6.tgz#fc756d2cc1ca493baf9b96bb27dd640ad8154cf1"
@ -14564,6 +14613,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
reselect@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
resolve-cwd@^2.0.0: resolve-cwd@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@ -15938,7 +15992,7 @@ swr@0.1.18:
dependencies: dependencies:
fast-deep-equal "2.0.1" fast-deep-equal "2.0.1"
symbol-observable@^1.1.0: symbol-observable@^1.1.0, symbol-observable@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==