Typescript (Type 'undefined' is not assignable to type) Why is my Array having a | undefined?

10,035

Solution 1

The problem was that in my filterByExchangeBase util function, I used .map instead of .filter which would result in some undefined objects in that array. Switching to filter made sure that only existing items would make it into the array.

...

Update: By changing .map to .filter, the price_quote updates didn't take

Refactored the logic to make sure that btcMarkets and ethMarkets aren't used if they will be empty.

export const combineExchangeData =
  (asset: string, { marketBTC, marketETH, marketUSD, marketUSDT, marketUSDC }: IGetMarketsRes) => {
    const btcBasedExchanges = marketBTC.filter((market: IMarketAsset) => market.base === asset);
    const ethBasedExchanges = marketETH.filter((market: IMarketAsset) => market.base === asset);
    const btcUSDTprices = marketUSDT.filter((market: IMarketAsset) => market.base === 'BTC');
    const btcUSDprices = marketUSD.filter((market: IMarketAsset) => market.base === 'BTC');
    const ethUSDTprices = marketUSDT.filter((market: IMarketAsset) => market.base === 'ETH');
    const ethUSDprices = marketUSD.filter((market: IMarketAsset) => market.base === 'ETH');

    const btcPricedMarkets = notBTCorETH(asset) ? filterCryptoBase(btcBasedExchanges, btcUSDTprices, btcUSDprices) : [];
    const ethPricedMarkets = notBTCorETH(asset) ? filterCryptoBase(ethBasedExchanges, ethUSDTprices, ethUSDprices) : [];

    const btcMarkets = R.not(R.isEmpty(btcPricedMarkets)) ? btcPricedMarkets.filter((market: IMarketAsset) => R.not(R.isNil(market))) : [];
    const ethMarkets = R.not(R.isEmpty(ethPricedMarkets)) ? ethPricedMarkets.filter((market: IMarketAsset) => R.not(R.isNil(market))) : [];

    const combinedMarkets = notBTCorETH(asset) ?
      btcMarkets.concat(ethMarkets).concat(marketUSD).concat(marketUSDC).concat(marketUSDT) :
      marketUSD.concat(marketUSDC).concat(marketUSDT);

    const filteredMarkets = filterByUSDbase(asset, combinedMarkets);

    if (R.isEmpty(filteredMarkets)) return [];

    return filteredMarkets.map((market: IMarketAsset) => ({
      ...market,
      price_quote: formatPrice(market.price_quote)
    }));
  };

export const filterCryptoBase =
  (exchanges: IMarketAsset[] | any, usdtExchanges: IMarketAsset[], usdExchanges: IMarketAsset[]) => {
    return exchanges.map((exchange: IMarketAsset) => {
      let basePriced = usdtExchanges.filter((btcExchange) => btcExchange.exchange === exchange.exchange)[0];
      if (!basePriced) {
        basePriced = usdExchanges.filter((btcExchange) => btcExchange.exchange === exchange.exchange)[0];
      }

      if (exchange && basePriced && exchange.price_quote && basePriced.price_quote) {
        const { price_quote: assetBtcPrice } = exchange; // Asset price in BTC/ETH
        const { price_quote: usdPrice } = basePriced; // BTC/ETH price in USDT/USD
        const price_quote = calculateBasePrice(assetBtcPrice, usdPrice).toString();

        return {
          ...exchange,
          price_quote
        }
      }

      return null;
    });
  }

Solution 2

Why is combinedMarkets an array either and object of type IMarketAsset or undefined

Because any of the marketXXX arrays, is an array of IMarketAsset | undefined (instead of just IMarketAsset.

Share:
10,035
Leon Gaban
Author by

Leon Gaban

Investor, Powerlifter, Crypto investor and global citizen You can also find me here: @leongaban | github | panga.ventures

Updated on July 11, 2022

Comments

  • Leon Gaban
    Leon Gaban almost 2 years

    I have an array called combinedMarkets which is either a combination of 5 or 3 different market arrays. All these arrays have the following interface IMarketAsset[]:

    export interface IMarketAsset {
      exchange: string;
      base: string;
      quote: string;
      price_quote: string;
      timestamp: string;
    }
    

    Here is where the typescript error occurs:

    const combinedMarkets = asset !== 'BTC' && asset !== 'ETH' ?
      btcMarkets.concat(ethMarkets).concat(marketUSD).concat(marketUSDC).concat(marketUSDT) :
      marketUSD.concat(marketUSDC).concat(marketUSDT);
    
    const filteredMarkets = combinedMarkets.length > 0 ? filterByUSDbase(asset, combinedMarkets) : [];
    

    Argument of type '({ price_quote: string; exchange: string; base: string; quote: string; timestamp: string; } | undefined)[]' is not assignable to parameter of type 'IMarketAsset[]'. Type '{ price_quote: string; exchange: string; base: string; quote: string; timestamp: string; } | undefined' is not assignable to type 'IMarketAsset'. Type 'undefined' is not assignable to type 'IMarketAsset'.ts(2345)

    const combinedMarkets: ({
      price_quote: string;
      exchange: string;
      base: string;
      quote: string;
      timestamp: string;
    } | undefined)[]
    

    enter image description here

    Why is combinedMarkets an array of either an object of type IMarketAsset or undefined?

    Full combineExchangeData Function

    // Filter by BTC, ETH, USD, USDT or USDC prices
    // If asset has BTC/ETH pairing, obtain exchange BTC/ETH price to calculate assets USD/USDT value
    export const combineExchangeData =
      (asset: string, { marketBTC, marketETH, marketUSD, marketUSDT, marketUSDC }: IGetMarketsRes) => {
        const btcBasedExchanges = marketBTC.filter((market: IMarketAsset) => market.base === asset);
        const ethBasedExchanges = marketETH.filter((market: IMarketAsset) => market.base === asset);
        const btcUSDTprices = marketUSDT.filter((market: IMarketAsset) => market.base === 'BTC');
        const btcUSDprices = marketUSD.filter((market: IMarketAsset) => market.base === 'BTC');
        const ethUSDTprices = marketUSDT.filter((market: IMarketAsset) => market.base === 'ETH');
        const ethUSDprices = marketUSD.filter((market: IMarketAsset) => market.base === 'ETH');
    
        const btcPricedMarkets = filterByExchangeBase(btcBasedExchanges, btcUSDTprices, btcUSDprices);
        const ethPricedMarkets = filterByExchangeBase(ethBasedExchanges, ethUSDTprices, ethUSDprices);
    
        const btcMarkets = btcPricedMarkets.filter((market) => R.not(R.isNil(market)));
        const ethMarkets = ethPricedMarkets.filter((market) => R.not(R.isNil(market)));
    
        const combinedMarkets = asset !== 'BTC' && asset !== 'ETH' ?
          btcMarkets.concat(ethMarkets).concat(marketUSD).concat(marketUSDC).concat(marketUSDT) :
          marketUSD.concat(marketUSDC).concat(marketUSDT);
    
        console.log('combinedMarkets', combinedMarkets);
        const filteredMarkets = combinedMarkets.length > 0 ? filterByUSDbase(asset, combinedMarkets) : [];
        console.log('filteredMarkets', filteredMarkets);
    
        if (R.isEmpty(filteredMarkets)) return [];
    
        return filteredMarkets.map((market: IMarketAsset) => {
          if (market) {
            return {
              ...market,
              price_quote: formatPrice(market.price_quote)
            }
          }
        });
      };
    

    Util functions

    Here are the 2 other util functions I use in the main function. Also I have narrowed down the problem to the btcMarkets and ethMarkets arrays. So looking at filterByExchangeBase.

    import * as R from 'ramda'
    
    import { USD_CURRENCIES } from '../shared/constants/api'
    import { IMarketAsset } from '../shared/types'
    
    const calculateBasePrice = (assetBtcPrice: string | number, btcPrice: string | number) => 
      (Number(assetBtcPrice) * Number(btcPrice)).toString();
    
    export const filterByExchangeBase =
      (exchanges: IMarketAsset[], usdtExchanges: IMarketAsset[], usdExchanges: IMarketAsset[]) =>
        exchanges.map((exchange) => {
          let basePriced = usdtExchanges.filter((btcExchange) => btcExchange.exchange === exchange.exchange)[0];
    
          if (!basePriced) {
            basePriced = usdExchanges.filter((btcExchange) => btcExchange.exchange === exchange.exchange)[0];
          }
    
          if (basePriced) {
            const { price_quote: assetBtcPrice } = exchange;
            const { price_quote: btcPrice } = basePriced;
    
            return {
              ...exchange,
              price_quote: calculateBasePrice(assetBtcPrice, btcPrice)
            }
          }
        });
    
    export const filterByUSDbase = (asset: string, combinedMarkets: IMarketAsset[] | undefined) => {
      if (!combinedMarkets) return [];
      return R.not(R.any(R.equals(asset))(USD_CURRENCIES))
        ? combinedMarkets.filter((marketAsset: IMarketAsset) => {
          if (marketAsset && marketAsset.base) {
            return marketAsset.base === asset;
          }
        }) : [];
    }
    
  • Leon Gaban
    Leon Gaban about 5 years
    Yeah I'm trying to figure out why that is, I think I've narrowed it down to combinedMarkets sometimes containing empty arrays during the concat part of the logic, so will try filtering out empty arrays.
  • Leon Gaban
    Leon Gaban about 5 years
    ? but each array is of the same type IMarketAsset so when I concated all of them, they are still of type IMarketAsset
  • Leon Gaban
    Leon Gaban about 5 years
    Could you post an example of this type You are correct in that I need a type that supports both IMarketAsset and undefined in an array.
  • Leon Gaban
    Leon Gaban about 5 years
    Ok my fix doesn't really fix the problem. I have to use export const filterCryptoBase = (exchanges: IMarketAsset[] | any to avoid a typescript error. Will offer this for bounty tomorrow.