message: react interface - cache token balances on dashboard with localStorage; add label if using cached data
diff: @@ -18,6 +18,8 @@ export const Dashboard = forwardRef(({ provider, signedInAddress, roles }, ref)
  const [fetchingTokens, setFetchingTokens] = useState(false);
  + const [isUsingCachedBalances, setIsUsingCachedBalances] = useState(false);
  +
  const [error, setError] = useState("");
  const isDealer = (roles[0] === true || roles[1] === true || roles[2] === true || roles[3] === true);
  @@ -35,18 +37,38 @@ export const Dashboard = forwardRef(({ provider, signedInAddress, roles }, ref)
  }));
  function handleRefresh() {
  + // clear localStorage
  + let localStorage = window.localStorage;
  + setIsUsingCachedBalances(false);
  + localStorage.setItem('token_balances', null);
  +
  setFetchingTokens(true);
  fetchBalances();
  }
  async function fetchBalances() {
  +
  + let newMyBalances = [];
  + let newMyIssuedTokens = [];
  +
  + let localStorage = window.localStorage;
  +
  + // use cached token balances if available and display notification on dashboard
  + let cachedTokenBalances = localStorage.getItem('token_balances');
  + if (cachedTokenBalances !== null && cachedTokenBalances !== "null") {
  + let tokenBalances = JSON.parse(cachedTokenBalances);
  + newMyBalances = tokenBalances.myBalances;
  + newMyIssuedTokens = tokenBalances.myIssuedTokens;
  + setIsUsingCachedBalances(true);
  +
  + // otherwise, fetch balances from blockchain network (slow when many tokens)
  + } else {
  try {
  // First, fetch number of unique tokens
  let numOfUniqueTokens = (await getNumOfUniqueTokens(provider)).toNumber();
  // Iterate over each tokenId and find balance of signed in address
  - let myBal = [];
  - let myIssued = [];
  +
  for (let i = 1; i <= numOfUniqueTokens; i++) {
  // Fetch token details
  @@ -92,21 +114,29 @@ export const Dashboard = forwardRef(({ provider, signedInAddress, roles }, ref)
  // Push token to myBalances or myIssuedTokens in state
  if (token.availableBalance > 0 || token.retiredBalance > 0) {
  - myBal.push(token);
  + newMyBalances.push(token);
  }
  if (token.issuer.toLowerCase() === signedInAddress.toLowerCase()) {
  - myIssued.push(token);
  + newMyIssuedTokens.push(token);
  }
  }
  - setMyBalances(myBal);
  - setMyIssuedTokens(myIssued);
  - setFetchingTokens(false);
  - setError("");
  + // add to localStorage
  + localStorage.setItem('token_balances', JSON.stringify({
  + myBalances: newMyBalances,
  + myIssuedTokens: newMyIssuedTokens
  + }));
  +
  } catch (error) {
  console.log(error);
  - setError("Could not connect to contract. Check your network settings in your wallet.");
  + setError("Could not connect to contract on the selected network. Check your wallet provider settings.");
  + }
  }
  +
  + setMyBalances(newMyBalances);
  + setMyIssuedTokens(newMyIssuedTokens);
  + setFetchingTokens(false);
  + setError("");
  }
  // If address and provider detected then fetch balances
  @@ -137,8 +167,9 @@ export const Dashboard = forwardRef(({ provider, signedInAddress, roles }, ref)
  />
  <h2>Dashboard</h2>
  - <p>View your token balances and tokens you've issued.</p>
  + <p className="mb-1">View your token balances and tokens you've issued.</p>
  + {isUsingCachedBalances && <small className="text-secondary">Using cached balances. Click "Dashboard" on the top left of the page to reload balances.</small>}
  <p className="text-danger">{error}</p>
  <div className={fetchingTokens ? "dimmed" : ""}>
  