mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Refactor token list (#8233)
* Refactor token list into standard container/component modules The token list has been moved into its own directory and split into separate container and component modules. Additional updates have been made to simplify the component logic as well. * Update token-list to use new React Context API
This commit is contained in:
parent
ade737e2a8
commit
966c38cd99
@ -1,190 +0,0 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import TokenCell from './token-cell'
|
||||
import TokenTracker from 'eth-token-tracker'
|
||||
import { connect } from 'react-redux'
|
||||
import { getSelectedAddress } from '../../selectors/selectors'
|
||||
import log from 'loglevel'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
tokens: state.metamask.tokens,
|
||||
userAddress: getSelectedAddress(state),
|
||||
assetImages: state.metamask.assetImages,
|
||||
}
|
||||
}
|
||||
|
||||
const defaultTokens = []
|
||||
import contracts from 'eth-contract-metadata'
|
||||
|
||||
for (const address in contracts) {
|
||||
const contract = contracts[address]
|
||||
if (contract.erc20) {
|
||||
contract.address = address
|
||||
defaultTokens.push(contract)
|
||||
}
|
||||
}
|
||||
|
||||
class TokenList extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
tokens: PropTypes.array.isRequired,
|
||||
userAddress: PropTypes.string.isRequired,
|
||||
network: PropTypes.string.isRequired,
|
||||
assetImages: PropTypes.object.isRequired,
|
||||
onTokenClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
tokens: [],
|
||||
isLoading: true,
|
||||
}
|
||||
|
||||
createFreshTokenTracker () {
|
||||
if (this.tracker) {
|
||||
// Clean up old trackers when refreshing:
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.balanceUpdater)
|
||||
this.tracker.removeListener('error', this.showError)
|
||||
}
|
||||
|
||||
if (!global.ethereumProvider) {
|
||||
return
|
||||
}
|
||||
const { userAddress } = this.props
|
||||
|
||||
this.tracker = new TokenTracker({
|
||||
userAddress,
|
||||
provider: global.ethereumProvider,
|
||||
tokens: this.props.tokens,
|
||||
pollingInterval: 8000,
|
||||
})
|
||||
|
||||
|
||||
// Set up listener instances for cleaning up
|
||||
this.balanceUpdater = this.updateBalances.bind(this)
|
||||
this.showError = (error) => {
|
||||
this.setState({ error, isLoading: false })
|
||||
}
|
||||
this.tracker.on('update', this.balanceUpdater)
|
||||
this.tracker.on('error', this.showError)
|
||||
|
||||
this.tracker.updateBalances()
|
||||
}
|
||||
|
||||
updateBalances = function (tokens) {
|
||||
this.setState({ tokens, isLoading: false })
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.createFreshTokenTracker()
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const {
|
||||
network: oldNet,
|
||||
userAddress: oldAddress,
|
||||
tokens,
|
||||
} = prevProps
|
||||
const {
|
||||
network: newNet,
|
||||
userAddress: newAddress,
|
||||
tokens: newTokens,
|
||||
} = this.props
|
||||
|
||||
const isLoading = newNet === 'loading'
|
||||
const missingInfo = !oldNet || !newNet || !oldAddress || !newAddress
|
||||
const sameUserAndNetwork = oldAddress === newAddress && oldNet === newNet
|
||||
const shouldUpdateTokens = isLoading || missingInfo || sameUserAndNetwork
|
||||
|
||||
const oldTokensLength = tokens ? tokens.length : 0
|
||||
const tokensLengthUnchanged = oldTokensLength === newTokens.length
|
||||
|
||||
if (tokensLengthUnchanged && shouldUpdateTokens) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({ isLoading: true })
|
||||
this.createFreshTokenTracker()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (!this.tracker) {
|
||||
return
|
||||
}
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.balanceUpdater)
|
||||
this.tracker.removeListener('error', this.showError)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { userAddress, assetImages, onTokenClick } = this.props
|
||||
const { tokens, isLoading, error } = this.state
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
height: '250px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '30px',
|
||||
}}
|
||||
>
|
||||
{this.context.t('loadingTokens')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
log.error(error)
|
||||
return (
|
||||
<div
|
||||
className="hotFix"
|
||||
style={{
|
||||
padding: '80px',
|
||||
}}
|
||||
>
|
||||
{this.context.t('troubleTokenBalances')}
|
||||
<span
|
||||
className="hotFix"
|
||||
style={{
|
||||
color: 'rgba(247, 134, 28, 1)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
global.platform.openWindow({
|
||||
url: `https://ethplorer.io/address/${userAddress}`,
|
||||
})
|
||||
}}
|
||||
>
|
||||
{this.context.t('here')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{tokens.map((tokenData, index) => {
|
||||
tokenData.image = assetImages[tokenData.address]
|
||||
return (
|
||||
<TokenCell key={index} {...tokenData} onClick={onTokenClick} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const TokenListContainer = connect(mapStateToProps)(TokenList)
|
||||
|
||||
TokenListContainer.propTypes = {
|
||||
onTokenClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default TokenListContainer
|
1
ui/app/components/app/token-list/index.js
Normal file
1
ui/app/components/app/token-list/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './token-list.container'
|
170
ui/app/components/app/token-list/token-list.component.js
Normal file
170
ui/app/components/app/token-list/token-list.component.js
Normal file
@ -0,0 +1,170 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import TokenTracker from 'eth-token-tracker'
|
||||
import { isEqual } from 'lodash'
|
||||
import contracts from 'eth-contract-metadata'
|
||||
|
||||
import { I18nContext } from '../../../contexts/i18n'
|
||||
import TokenCell from '../token-cell'
|
||||
|
||||
const defaultTokens = []
|
||||
for (const address in contracts) {
|
||||
const contract = contracts[address]
|
||||
if (contract.erc20) {
|
||||
contract.address = address
|
||||
defaultTokens.push(contract)
|
||||
}
|
||||
}
|
||||
|
||||
class TokenList extends Component {
|
||||
static contextType = I18nContext
|
||||
|
||||
static propTypes = {
|
||||
assetImages: PropTypes.object.isRequired,
|
||||
network: PropTypes.string.isRequired,
|
||||
onTokenClick: PropTypes.func.isRequired,
|
||||
tokens: PropTypes.array.isRequired,
|
||||
userAddress: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
tokensLoading: false,
|
||||
tokensWithBalances: [],
|
||||
}
|
||||
}
|
||||
|
||||
constructTokenTracker () {
|
||||
const { network, tokens, userAddress } = this.props
|
||||
if (!tokens || !tokens.length) {
|
||||
this.setState({
|
||||
tokensLoading: false,
|
||||
tokensWithBalances: [],
|
||||
})
|
||||
return
|
||||
}
|
||||
this.setState({ tokensLoading: true })
|
||||
|
||||
if (!userAddress || network === 'loading' || !global.ethereumProvider) {
|
||||
return
|
||||
}
|
||||
|
||||
const updateBalances = (tokensWithBalances) => {
|
||||
this.setState({
|
||||
error: null,
|
||||
tokensLoading: false,
|
||||
tokensWithBalances,
|
||||
})
|
||||
}
|
||||
const showError = (error) => {
|
||||
this.setState({
|
||||
error,
|
||||
tokensLoading: false,
|
||||
})
|
||||
}
|
||||
|
||||
this.tokenTracker = new TokenTracker({
|
||||
userAddress,
|
||||
provider: global.ethereumProvider,
|
||||
tokens: tokens,
|
||||
pollingInterval: 8000,
|
||||
})
|
||||
|
||||
this.tokenTracker.on('update', updateBalances)
|
||||
this.tokenTracker.on('error', showError)
|
||||
this.tokenTracker.updateBalances()
|
||||
}
|
||||
|
||||
stopTokenTracker () {
|
||||
if (this.tokenTracker) {
|
||||
this.tokenTracker.stop()
|
||||
this.tokenTracker.removeAllListeners('update')
|
||||
this.tokenTracker.removeAllListeners('error')
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.constructTokenTracker()
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { network, tokens, userAddress } = this.props
|
||||
if (
|
||||
isEqual(tokens, prevProps.tokens) &&
|
||||
userAddress === prevProps.userAddress &&
|
||||
network === prevProps.network
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.stopTokenTracker()
|
||||
this.constructTokenTracker()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.stopTokenTracker()
|
||||
}
|
||||
|
||||
render () {
|
||||
const t = this.context
|
||||
const { error, tokensLoading, tokensWithBalances } = this.state
|
||||
const { assetImages, network, onTokenClick, userAddress } = this.props
|
||||
if (network === 'loading' || tokensLoading) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
height: '250px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '30px',
|
||||
}}
|
||||
>
|
||||
{t('loadingTokens')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div
|
||||
className="hotFix"
|
||||
style={{
|
||||
padding: '80px',
|
||||
}}
|
||||
>
|
||||
{t('troubleTokenBalances')}
|
||||
<span
|
||||
className="hotFix"
|
||||
style={{
|
||||
color: 'rgba(247, 134, 28, 1)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
global.platform.openWindow({
|
||||
url: `https://ethplorer.io/address/${userAddress}`,
|
||||
})
|
||||
}}
|
||||
>
|
||||
{t('here')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{tokensWithBalances.map((tokenData, index) => {
|
||||
tokenData.image = assetImages[tokenData.address]
|
||||
return (
|
||||
<TokenCell key={index} {...tokenData} onClick={onTokenClick} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TokenList
|
21
ui/app/components/app/token-list/token-list.container.js
Normal file
21
ui/app/components/app/token-list/token-list.container.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { getSelectedAddress } from '../../../selectors/selectors'
|
||||
import TokenList from './token-list.component'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
tokens: state.metamask.tokens,
|
||||
userAddress: getSelectedAddress(state),
|
||||
assetImages: state.metamask.assetImages,
|
||||
}
|
||||
}
|
||||
|
||||
const TokenListContainer = connect(mapStateToProps)(TokenList)
|
||||
|
||||
TokenListContainer.propTypes = {
|
||||
onTokenClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default TokenListContainer
|
Loading…
Reference in New Issue
Block a user