From 5606805a8afe81007d9d66ce3ed5d242c774eadf Mon Sep 17 00:00:00 2001 From: Artem Date: Mon, 20 Dec 2021 23:39:11 +0300 Subject: [PATCH] Fix blank page on loading ERC tokens; use indexedDb instead of localStorage to store tokens list (#100) --- src/components/ERC1155_Pool.tsx | 22 +++++--- src/components/ERC20_Pool.tsx | 20 ++++--- src/components/ERC721_Pool.tsx | 20 ++++--- src/hooks/ERC1155_Pool.ts | 25 ++++++--- src/hooks/ERC20_Pool.ts | 25 ++++++--- src/hooks/ERC721_Pool.ts | 25 ++++++--- src/pages/AddressPage/TokenInfo.tsx | 6 +-- src/pages/ERC20List/ERC20Table.tsx | 2 +- src/utils/indexedDB.ts | 84 +++++++++++++++++++++++++++++ 9 files changed, 188 insertions(+), 41 deletions(-) create mode 100644 src/utils/indexedDB.ts diff --git a/src/components/ERC1155_Pool.tsx b/src/components/ERC1155_Pool.tsx index 7864f8b..011fe6f 100644 --- a/src/components/ERC1155_Pool.tsx +++ b/src/components/ERC1155_Pool.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from "react"; import { getAllERC1155 } from "src/api/client"; import { setERC1155Pool, ERC1155 } from "src/hooks/ERC1155_Pool"; +import { IndexedDbKeyPath, IndexedDbStore, saveToIndexedDB } from "../utils/indexedDB"; export function ERC1155_Pool() { useEffect(() => { @@ -8,14 +9,21 @@ export function ERC1155_Pool() { const getRates = async () => { try { - const erc1155: ERC1155[] = await getAllERC1155(); const erc1155Map = {} as Record; - erc1155.forEach((i: any) => { - erc1155Map[i.address] = i; - }); - - window.localStorage.setItem("ERC1155_Pool", JSON.stringify(erc1155Map)); + let erc1155: ERC1155[] = await getAllERC1155(); + erc1155 = erc1155.map(item => { + erc1155Map[item.address] = item; + return { + [IndexedDbKeyPath]: item.address, + ...item + } + }) setERC1155Pool(erc1155Map); + await saveToIndexedDB(IndexedDbStore.ERC1155Pool, erc1155) + localStorage.setItem(IndexedDbStore.ERC1155Pool, '1') + console.log(`ERC1155 tokens is updated, count: ${erc1155.length}`) + } catch (e) { + console.error('Cannot update ERC1155 tokens', (e as Error).message) } finally { clearTimeout(tId); tId = window.setTimeout(getRates, 10 * 60 * 1e3); @@ -26,7 +34,7 @@ export function ERC1155_Pool() { () => { getRates(); }, - window.localStorage.getItem("ERC1155_Pool") ? 2200 : 0 + window.localStorage.getItem(IndexedDbStore.ERC1155Pool) ? 2200 : 0 ); return () => { diff --git a/src/components/ERC20_Pool.tsx b/src/components/ERC20_Pool.tsx index e6553b1..dcdb7ad 100644 --- a/src/components/ERC20_Pool.tsx +++ b/src/components/ERC20_Pool.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from "react"; import { getAllERC20 } from "src/api/client"; import { setERC20Pool, Erc20 } from "src/hooks/ERC20_Pool"; +import { IndexedDbKeyPath, IndexedDbStore, saveToIndexedDB } from "../utils/indexedDB"; export function ERC20_Pool() { useEffect(() => { @@ -8,14 +9,21 @@ export function ERC20_Pool() { const getRates = async () => { try { - const erc20: Erc20[] = await getAllERC20(); + let erc20: Erc20[] = await getAllERC20(); const erc20Map = {} as Record; - erc20.forEach((i: any) => { - erc20Map[i.address] = i; + erc20 = erc20.map((item) => { + erc20Map[item.address] = item; + return { + [IndexedDbKeyPath]: item.address, + ...item + } }); - - window.localStorage.setItem("ERC20_Pool", JSON.stringify(erc20Map)); setERC20Pool(erc20Map); + await saveToIndexedDB(IndexedDbStore.ERC20Pool, erc20) + localStorage.setItem(IndexedDbStore.ERC20Pool, '1') + console.log(`ERC20 tokens updated, count: ${erc20.length}`) + } catch (e) { + console.error('Cannot update ERC20 tokens', (e as Error).message) } finally { window.clearTimeout(tId); tId = window.setTimeout(getRates, 10 * 60 * 1e3); @@ -26,7 +34,7 @@ export function ERC20_Pool() { () => { getRates(); }, - window.localStorage.getItem("ERC20_Pool") ? 2000 : 0 + window.localStorage.getItem(IndexedDbStore.ERC20Pool) ? 2000 : 0 ); return () => { diff --git a/src/components/ERC721_Pool.tsx b/src/components/ERC721_Pool.tsx index 6a143e8..b58e98f 100644 --- a/src/components/ERC721_Pool.tsx +++ b/src/components/ERC721_Pool.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from "react"; import { getAllERC721 } from "src/api/client"; import { setERC721Pool, ERC721 } from "src/hooks/ERC721_Pool"; +import { IndexedDbKeyPath, IndexedDbStore, saveToIndexedDB } from "../utils/indexedDB"; export function ERC721_Pool() { useEffect(() => { @@ -8,14 +9,21 @@ export function ERC721_Pool() { const getRates = async () => { try { - const erc721: ERC721[] = await getAllERC721(); + let erc721: ERC721[] = await getAllERC721(); const erc721Map = {} as Record; - erc721.forEach((i: any) => { - erc721Map[i.address] = i; + erc721 = erc721.map((item) => { + erc721Map[item.address] = item; + return { + [IndexedDbKeyPath]: item.address, + ...item + } }); - - window.localStorage.setItem("ERC721_Pool", JSON.stringify(erc721Map)); setERC721Pool(erc721Map); + await saveToIndexedDB(IndexedDbStore.ERC721Pool, erc721) + localStorage.setItem(IndexedDbStore.ERC721Pool, '1') + console.log(`ERC721 tokens is updated, count: ${erc721.length}`) + } catch (e) { + console.error('Cannot update ERC721 tokens', (e as Error).message) } finally { clearTimeout(tId); tId = window.setTimeout(getRates, 10 * 60 * 1e3); @@ -26,7 +34,7 @@ export function ERC721_Pool() { () => { getRates(); }, - window.localStorage.getItem("ERC721_Pool") ? 2100 : 0 + window.localStorage.getItem(IndexedDbStore.ERC721Pool) ? 2100 : 0 ); return () => { diff --git a/src/hooks/ERC1155_Pool.ts b/src/hooks/ERC1155_Pool.ts index dd4b7c4..36fcb0b 100644 --- a/src/hooks/ERC1155_Pool.ts +++ b/src/hooks/ERC1155_Pool.ts @@ -1,5 +1,6 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { singletonHook } from "react-singleton-hook"; +import { IndexedDbStore, loadFromIndexedDB } from "../utils/indexedDB"; const initValue: ERC1155_Pool = {}; @@ -8,12 +9,24 @@ let globalSetMode = () => { }; export const useERC1155Pool = singletonHook(initValue, () => { - const pool = - (JSON.parse( - window.localStorage.getItem("ERC1155_Pool") || "{}" - ) as ERC1155_Pool) || initValue; + const getPool = async () => { + try { + const erc1155 = await loadFromIndexedDB(IndexedDbStore.ERC1155Pool) + const erc1155Map = {} as Record; + erc1155.forEach(item => { + erc1155Map[item.address] = item; + }) + setMode(erc1155Map) + } catch (e) { + console.error('Cannot get ERC1155 records: ', (e as Error).message) + } + } - const [mode, setMode] = useState(pool); + useEffect(() => { + getPool() + }, []) + + const [mode, setMode] = useState(initValue); //@ts-ignore globalSetMode = setMode; return mode; diff --git a/src/hooks/ERC20_Pool.ts b/src/hooks/ERC20_Pool.ts index 6ff33b2..228bdc1 100644 --- a/src/hooks/ERC20_Pool.ts +++ b/src/hooks/ERC20_Pool.ts @@ -1,5 +1,6 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { singletonHook } from "react-singleton-hook"; +import { IndexedDbStore, loadFromIndexedDB } from "../utils/indexedDB"; const initValue: ERC20_Pool = {}; @@ -8,12 +9,24 @@ let globalSetMode = () => { }; export const useERC20Pool = singletonHook(initValue, () => { - const pool = - (JSON.parse( - window.localStorage.getItem("ERC20_Pool") || "{}" - ) as ERC20_Pool) || initValue; + const getPool = async () => { + try { + const erc20 = await loadFromIndexedDB(IndexedDbStore.ERC20Pool) + const erc20Map = {} as Record; + erc20.forEach(item => { + erc20Map[item.address] = item; + }) + setMode(erc20Map) + } catch (e) { + console.error('Cannot get ERC20 records: ', (e as Error).message) + } + } - const [mode, setMode] = useState(pool); + useEffect(() => { + getPool() + }, []) + + const [mode, setMode] = useState(initValue); //@ts-ignore globalSetMode = setMode; return mode; diff --git a/src/hooks/ERC721_Pool.ts b/src/hooks/ERC721_Pool.ts index 4097116..1f9dcd4 100644 --- a/src/hooks/ERC721_Pool.ts +++ b/src/hooks/ERC721_Pool.ts @@ -1,5 +1,6 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { singletonHook } from "react-singleton-hook"; +import { IndexedDbStore, loadFromIndexedDB } from "../utils/indexedDB"; const initValue: ERC721_Pool = {}; @@ -8,12 +9,24 @@ let globalSetMode = () => { }; export const useERC721Pool = singletonHook(initValue, () => { - const pool = - (JSON.parse( - window.localStorage.getItem("ERC721_Pool") || "{}" - ) as ERC721_Pool) || initValue; + const getPool = async () => { + try { + const erc721 = await loadFromIndexedDB(IndexedDbStore.ERC721Pool) + const erc721Map = {} as Record; + erc721.forEach(item => { + erc721Map[item.address] = item; + }) + setMode(erc721Map) + } catch (e) { + console.error('Cannot get ERC721 records: ', (e as Error).message) + } + } - const [mode, setMode] = useState(pool); + useEffect(() => { + getPool() + }, []) + + const [mode, setMode] = useState(initValue); //@ts-ignore globalSetMode = setMode; return mode; diff --git a/src/pages/AddressPage/TokenInfo.tsx b/src/pages/AddressPage/TokenInfo.tsx index 170f02a..61a67ed 100644 --- a/src/pages/AddressPage/TokenInfo.tsx +++ b/src/pages/AddressPage/TokenInfo.tsx @@ -45,7 +45,7 @@ export function TokensInfo(props: { value: Token[] }) { const erc20Tokens = value .filter((i) => filterWithBalance(i.balance)) - .filter((i) => i.isERC20) + .filter((i) => i.isERC20 && erc20Map[i.tokenAddress]) .map((item) => ({ ...item, symbol: erc20Map[item.tokenAddress].symbol, @@ -55,7 +55,7 @@ export function TokensInfo(props: { value: Token[] }) { const erc721Tokens = value .filter((i) => filterWithBalance(i.balance)) - .filter((i) => i.isERC721) + .filter((i) => i.isERC721 && erc721Map[i.tokenAddress]) .map((item) => ({ ...item, symbol: erc721Map[item.tokenAddress].symbol, @@ -64,7 +64,7 @@ export function TokensInfo(props: { value: Token[] }) { const erc1155Tokens = value .filter((i) => filterWithBalance(i.balance)) - .filter((i) => i.isERC1155) + .filter((i) => i.isERC1155 && erc1155Map[i.tokenAddress]) .map((item) => ({ ...item, symbol: erc1155Map[item.tokenAddress].symbol, diff --git a/src/pages/ERC20List/ERC20Table.tsx b/src/pages/ERC20List/ERC20Table.tsx index 5040515..4e6f551 100644 --- a/src/pages/ERC20List/ERC20Table.tsx +++ b/src/pages/ERC20List/ERC20Table.tsx @@ -152,7 +152,7 @@ function getColumns(props: any) { render: (data: Erc20) =>
, }, { - property: "totalSupply", + property: "circulatingSupply", size: "small", resizeable: false, header: ( diff --git a/src/utils/indexedDB.ts b/src/utils/indexedDB.ts new file mode 100644 index 0000000..ff5c4c7 --- /dev/null +++ b/src/utils/indexedDB.ts @@ -0,0 +1,84 @@ +export enum IndexedDbStore { + ERC1155Pool = 'ERC1155_Pool', + ERC20Pool = 'ERC20_Pool', + ERC721Pool = 'ERC721_Pool', +} + +export const IndexedDbKeyPath = '_id' + +const DbVersion = 1 + +export const saveToIndexedDB = (storeName: IndexedDbStore, objects: any[]) => { + return new Promise( + function(resolve, reject) { + const dbRequest = indexedDB.open(storeName, DbVersion); + + dbRequest.onerror = function() { + reject(Error('IndexedDB error')); + }; + + dbRequest.onupgradeneeded = function(event) { + // @ts-ignore + const db = event.target.result; + db.createObjectStore(storeName, { keyPath: IndexedDbKeyPath }); + }; + + dbRequest.onsuccess = function() { + let db = dbRequest.result; + db.onversionchange = function() { + db.close(); + console.log('Database is outdated, please reload the page'); + }; + const transaction = db.transaction([storeName], 'readwrite'); + const objectStore = transaction.objectStore(storeName); + objects.forEach((item, i, arr) => { + const objectRequest = objectStore.put(item); + if (i === arr.length - 1) { + objectRequest.onsuccess = function() { + resolve('Data saved'); + }; + } + }) + }; + + dbRequest.onblocked = function() { + reject(Error(`IndexedDB ${storeName} is blocked`)); + }; + } + ); +} + +export const loadFromIndexedDB = (storeName: IndexedDbStore): Promise => { + return new Promise( + function(resolve, reject) { + const dbRequest = indexedDB.open(storeName, DbVersion); + + dbRequest.onerror = function() { + reject(Error('IndexedDB error')); + }; + + dbRequest.onupgradeneeded = function(event) { + // @ts-ignore + event.target.transaction.abort(); + reject(Error('Not found')); + }; + + dbRequest.onsuccess = function(event) { + // @ts-ignore + const database = event.target.result; + const transaction = database.transaction([storeName]); + const objectStore = transaction.objectStore(storeName); + const objectRequest = objectStore.getAll(); + + objectRequest.onerror = function(e: Error) { + reject(Error(`Indexed db error: ${e.message}`)); + }; + + objectRequest.onsuccess = function(event: any) { + if (objectRequest.result) resolve(objectRequest.result); + else reject(Error('Objects is not found')); + }; + }; + } + ); +}