'How to map large data in nextjs when in viewport?
I want to make make a dropdown where a user can select an erc20 token from a tokenlist in Nextjs.
I tried a regular mapping function on the token list but then the site doesn't respond and is very slow because the tokenlist.json. I would like to render the data when in viewport. How can I achieve this?
I would like to make it fast, like in the token select modal in Uniswap
I used nextjs Image and this loads the token image when in view but it is still slow because it needs to render the token name and symbol
This is how I fetch the tokenlist and render it:
import { Fragment, useEffect, useState } from 'react';
import { Combobox, Transition } from '@headlessui/react';
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid';
import { PlusSmIcon } from '@heroicons/react/outline';
import axios from 'axios';
import tokensJson from '../web3/tokens.json';
import Image from 'next/image';
export default function SelectErc20() {
const [selected, setSelected] = useState(tokensJson.tokens[0]);
const [tokenlist, setTokenlist] = useState([]);
const [query, setQuery] = useState('');
const filteredTokens =
query === ''
? tokenlist
: tokenlist.filter((token) =>
token.name
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.toLowerCase().replace(/\s+/g, ''))
);
useEffect(() => {
axios
.get('https://tokens.coingecko.com/uniswap/all.json')
.then((res) => {
setTokenlist(res.data.tokens);
})
.catch(setTokenlist(tokensJson.tokens));
}, []);
return (
<div className="flex items-center space-x-3">
<img src={selected.logoURI} alt="token" className="h-6 w-6" />
<div className="w-64">
<Combobox value={selected} onChange={setSelected}>
<div className="relative mt-1">
<div className="relative w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-emerald-300 sm:text-sm">
<Combobox.Input
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
displayValue={(token) => token.name}
onChange={(event) => setQuery(event.target.value)}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
<SelectorIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</Combobox.Button>
</div>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<a
href="#"
className="relative mb-3 flex select-none items-center space-x-3 py-2 px-4 text-gray-700 hover:bg-neutral-100"
>
<PlusSmIcon className="h-5 w-5" />
<span>Add custom token</span>
</a>
{filteredTokens.length === 0 && query !== '' ? (
<div className="relative select-none py-2 px-4 text-gray-700">
<span>Nothing found..</span>
</div>
) : (
filteredTokens.map((token) => (
<Combobox.Option
key={token.address}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-10 pr-4 ${
active ? 'bg-emerald-600 text-white' : 'text-gray-900'
}`
}
value={token}
>
{({ selected, active }) => (
<div className="flex items-center justify-between">
<div className="flex items-center truncate">
<Image
src={token.logoURI}
alt={token.name}
width="24"
height="24"
className="mr-3"
/>
<span
className={`block truncate ${
selected ? 'font-medium' : 'font-normal'
}`}
>
{token.name}
</span>
</div>
<span
className={`block text-xs text-gray-400 ${
selected ? 'font-medium' : 'font-normal'
} ${active ? 'text-white' : null}`}
>
{token.symbol}
</span>
{selected ? (
<span
className={`absolute inset-y-0 left-0 flex items-center pl-3 ${
active ? 'text-white' : 'text-emerald-600'
}`}
>
<CheckIcon
className="h-5 w-5"
aria-hidden="true"
/>
</span>
) : null}
</div>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Transition>
</div>
</Combobox>
</div>
</div>
);
}
Solution 1:[1]
It's because you're rendering too much HTML node, your navigator can't paint it.
In order to do what you need, you must use what we call a 'virtual list'.
There are few libraries to virtualize, you're not the first.
Look at for exemple React Window
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | BENARD Patrick |
