diff --git a/package.json b/package.json
index 55cd725b..c8dffd7d 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
"react-qr-reader": "^2.2.1",
"react-redux": "7.1.1",
"react-router-dom": "5.1.2",
+ "react-window": "^1.8.5",
"recompose": "^0.30.0",
"redux": "4.0.4",
"redux-actions": "^2.6.5",
@@ -114,7 +115,7 @@
"eslint-plugin-import": "2.18.2",
"eslint-plugin-jest": "22.17.0",
"eslint-plugin-jsx-a11y": "6.2.3",
- "eslint-plugin-react": "7.15.0",
+ "eslint-plugin-react": "7.14.3",
"ethereumjs-abi": "0.6.8",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "4.2.0",
diff --git a/src/logic/tokens/api/fetchTokenList.js b/src/logic/tokens/api/fetchTokenList.js
index 63b3b9eb..82cbb7ba 100644
--- a/src/logic/tokens/api/fetchTokenList.js
+++ b/src/logic/tokens/api/fetchTokenList.js
@@ -6,7 +6,11 @@ const fetchTokenList = () => {
const apiUrl = getRelayUrl()
const url = `${apiUrl}/tokens`
- return axios.get(url)
+ return axios.get(url, {
+ params: {
+ limit: 300,
+ },
+ })
}
export default fetchTokenList
diff --git a/src/routes/load/container/Load.jsx b/src/routes/load/container/Load.jsx
index 181d0ec8..82b825e0 100644
--- a/src/routes/load/container/Load.jsx
+++ b/src/routes/load/container/Load.jsx
@@ -19,7 +19,7 @@ type Props = SelectorProps & Actions
export const loadSafe = async (
safeName: string,
safeAddress: string,
- owners: Array,
+ owners: Array<*>,
addSafe: Function,
) => {
const safeProps = await buildSafe(safeAddress, safeName)
diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField/index.jsx b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField/index.jsx
index 873df0d0..61264c67 100644
--- a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField/index.jsx
+++ b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField/index.jsx
@@ -12,6 +12,7 @@ import SelectField from '~/components/forms/SelectField'
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
import { required } from '~/components/forms/validator'
import { type Token } from '~/logic/tokens/store/model/token'
+import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { selectedTokenStyles, selectStyles } from './style'
type SelectFieldProps = {
@@ -35,7 +36,7 @@ const SelectedToken = ({ token, classes }: SelectedTokenProps) => (
>
) : (
@@ -54,7 +55,7 @@ const TokenSelectField = ({ tokens, classes, initialValue }: SelectFieldProps) =
const [initialToken, setInitialToken] = useState('')
useEffect(() => {
- const selectedToken = tokens.find(token => token.name === initialValue)
+ const selectedToken = tokens.find((token) => token.name === initialValue)
setInitialToken(selectedToken || '')
}, [initialValue])
@@ -64,16 +65,16 @@ const TokenSelectField = ({ tokens, classes, initialValue }: SelectFieldProps) =
component={SelectField}
classes={{ selectMenu: classes.selectMenu }}
validate={required}
- renderValue={token => }
+ renderValue={(token) => }
initialValue={initialToken}
displayEmpty
>
- {tokens.map(token => (
+ {tokens.map((token) => (
))}
diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.jsx b/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.jsx
new file mode 100644
index 00000000..9d3e3b73
--- /dev/null
+++ b/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.jsx
@@ -0,0 +1,57 @@
+// @flow
+import React, { memo } from 'react'
+import { List, Set } from 'immutable'
+import { withStyles } from '@material-ui/core/styles'
+import ListItem from '@material-ui/core/ListItem'
+import ListItemIcon from '@material-ui/core/ListItemIcon'
+import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
+import ListItemText from '@material-ui/core/ListItemText'
+import Switch from '@material-ui/core/Switch'
+import Img from '~/components/layout/Img'
+import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
+import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
+import { type Token } from '~/logic/tokens/store/model/token'
+import { styles } from './style'
+
+export const TOGGLE_TOKEN_TEST_ID = 'toggle-token-btn'
+
+type Props = {
+ data: {
+ activeTokensAddresses: Set,
+ tokens: List,
+ onSwitch: Function,
+ },
+ style: Object,
+ index: number,
+ classes: Object,
+}
+
+const TokenRow = memo(({
+ data, index, classes, style,
+}: Props) => {
+ const { tokens, activeTokensAddresses, onSwitch } = data
+ const token: Token = tokens.get(index)
+ const isActive = activeTokensAddresses.has(token.address)
+
+ return (
+
+
+
+
+
+
+ {token.address !== ETH_ADDRESS && (
+
+
+
+ )}
+
+
+ )
+})
+
+export default withStyles(styles)(TokenRow)
diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.jsx b/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.jsx
index 8e6dc9a2..c2ae1f9e 100644
--- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.jsx
+++ b/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.jsx
@@ -2,30 +2,23 @@
import * as React from 'react'
import { List, Set } from 'immutable'
import cn from 'classnames'
+import { FixedSizeList } from 'react-window'
import SearchBar from 'material-ui-search-bar'
import { withStyles } from '@material-ui/core/styles'
import MuiList from '@material-ui/core/List'
-import ListItem from '@material-ui/core/ListItem'
-import ListItemIcon from '@material-ui/core/ListItemIcon'
import CircularProgress from '@material-ui/core/CircularProgress'
-import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
-import ListItemText from '@material-ui/core/ListItemText'
-import Switch from '@material-ui/core/Switch'
import Search from '@material-ui/icons/Search'
-import Img from '~/components/layout/Img'
import Block from '~/components/layout/Block'
import Button from '~/components/layout/Button'
import Divider from '~/components/layout/Divider'
import Hairline from '~/components/layout/Hairline'
import Spacer from '~/components/Spacer'
import Row from '~/components/layout/Row'
-import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
import { type Token } from '~/logic/tokens/store/model/token'
-import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
+import TokenRow from './TokenRow'
import { styles } from './style'
export const ADD_CUSTOM_TOKEN_BUTTON_TEST_ID = 'add-custom-token-btn'
-export const TOGGLE_TOKEN_TEST_ID = 'toggle-token-btn'
type Props = {
classes: Object,
@@ -110,6 +103,18 @@ class Tokens extends React.Component {
}
}
+ createItemData = (tokens, activeTokensAddresses) => ({
+ tokens,
+ activeTokensAddresses,
+ onSwitch: this.onSwitch,
+ })
+
+ getItemKey = (index, { tokens }) => {
+ const token: Token = tokens.get(index)
+
+ return token.address
+ }
+
render() {
const { classes, tokens, setActiveScreen } = this.props
const { filter, activeTokensAddresses } = this.state
@@ -122,6 +127,7 @@ class Tokens extends React.Component {
const switchToAddCustomTokenScreen = () => setActiveScreen('addCustomToken')
const filteredTokens = filterBy(filter, tokens)
+ const itemData = this.createItemData(filteredTokens, activeTokensAddresses)
return (
<>
@@ -152,34 +158,25 @@ class Tokens extends React.Component {
-
- {!tokens.size && (
-
-
-
- )}
- {filteredTokens.map((token: Token) => {
- const isActive = activeTokensAddresses.has(token.address)
-
- return (
-
-
-
-
-
- {token.address !== ETH_ADDRESS && (
-
-
-
- )}
-
- )
- })}
-
+ {!tokens.size && (
+
+
+
+ )}
+ {tokens.size > 0 && (
+
+
+ {TokenRow}
+
+
+ )}
>
)
}
diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.js b/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.js
index 578e72ef..4ba04d2a 100644
--- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.js
+++ b/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.js
@@ -56,6 +56,8 @@ export const styles = () => ({
},
tokenIcon: {
marginRight: md,
+ height: '28px',
+ width: '28px',
},
progressContainer: {
width: '100%',
diff --git a/yarn.lock b/yarn.lock
index 4bb821b4..32a4e9d8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7335,20 +7335,20 @@ eslint-plugin-jsx-a11y@6.2.3:
has "^1.0.3"
jsx-ast-utils "^2.2.1"
-eslint-plugin-react@7.15.0:
- version "7.15.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.15.0.tgz#4808b19cf7b4c439454099d4eb8f0cf0e9fe31dd"
- integrity sha512-NbIh/yVXoltm8Df28PiPRanfCZAYubGqXU391MTCpW955Vum7S0nZdQYXGAvDh9ye4aNCmOR6YcYZsfMbEQZQA==
+eslint-plugin-react@7.14.3:
+ version "7.14.3"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz#911030dd7e98ba49e1b2208599571846a66bdf13"
+ integrity sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==
dependencies:
array-includes "^3.0.3"
doctrine "^2.1.0"
has "^1.0.3"
- jsx-ast-utils "^2.2.1"
+ jsx-ast-utils "^2.1.0"
object.entries "^1.1.0"
object.fromentries "^2.0.0"
object.values "^1.1.0"
prop-types "^15.7.2"
- resolve "^1.12.0"
+ resolve "^1.10.1"
eslint-scope@^3.7.1:
version "3.7.3"
@@ -11359,7 +11359,7 @@ jss@10.0.0-alpha.25:
is-in-browser "^1.1.3"
tiny-warning "^1.0.2"
-jsx-ast-utils@^2.2.1:
+jsx-ast-utils@^2.1.0, jsx-ast-utils@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz#4d4973ebf8b9d2837ee91a8208cc66f3a2776cfb"
integrity sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==
@@ -12133,6 +12133,11 @@ memdown@~3.0.0:
ltgt "~2.2.0"
safe-buffer "~5.1.1"
+"memoize-one@>=3.1.1 <6":
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
+ integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
+
memoize-one@^5.0.0:
version "5.0.5"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.5.tgz#8cd3809555723a07684afafcd6f756072ac75d7e"
@@ -14899,6 +14904,14 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
+react-window@^1.8.5:
+ version "1.8.5"
+ resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1"
+ integrity sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q==
+ dependencies:
+ "@babel/runtime" "^7.0.0"
+ memoize-one ">=3.1.1 <6"
+
react@16.10.1:
version "16.10.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.10.1.tgz#967c1e71a2767dfa699e6ba702a00483e3b0573f"
@@ -15592,7 +15605,7 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.3.2
dependencies:
path-parse "^1.0.6"
-resolve@^1.12.0:
+resolve@^1.10.1, resolve@^1.12.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==