All files / src WorldPrice.jsx

63.16% Statements 24/38
61.9% Branches 13/21
44.44% Functions 4/9
64.86% Lines 24/37
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105              1x 1x                                                         9x                     9x 9x 9x 9x 9x     9x   8x             1x 1x                             9x 9x       9x 9x     9x 9x 9x 9x         9x 9x   9x 9x      
// @flow
import * as React from 'react';
import fetch from 'unfetch';
import ls from 'local-storage';
import CURRENCY from './json/currencies.json';
import { getCurrencyFromBrowserLocale, formatAmountForCurrency } from './utils';
 
const NAMESPACE = 'react-world-price';
const ONE_DAY_AGO = 1000 * 60 * 60 * 24;
 
type Props = {
  amount: number,
  baseCurrency: string,
  displayCurrency: string,
  hideCents: boolean,
  rounding: number => number,
  unwrap: boolean,
};
 
type State = {
  rates: Object,
};
 
export default class Price extends React.Component<Props, State> {
  static defaultProps = {
    baseCurrency: 'USD',
    displayCurrency: getCurrencyFromBrowserLocale(),
    hideCents: false,
    rounding: Math.round,
    unwrap: false,
  };
 
  state = {
    rates: {},
  };
 
  componentDidMount() {
    this.fetchRates(this.props.baseCurrency);
  }
 
  // baseCurrency shouldn't change, but just in case
  componentWillReceiveProps(nextProps: Props) {
    if (nextProps.baseCurrency !== this.props.baseCurrency) {
      this.fetchRates(nextProps.baseCurrency);
    }
  }
 
  fetchRates(base: string) {
    const now = new Date().getTime();
    const dateKey = `${NAMESPACE}-date-${base}`;
    const rateKey = `${NAMESPACE}-rates-${base}`;
    const localDate = ls(dateKey);
    Iif (localDate && now - Number(localDate) < ONE_DAY_AGO) {
      const localRates = ls(rateKey);
      this.setState({ rates: localRates });
    } else if (window.__REACT_WORLD_PRICE_FETCHING__) {
      // kinda whack, but better than firing an XHR for every instance on first load
      setTimeout(() => {
        this.fetchRates(base);
      }, 50);
    } else {
      // if this is the first instance to react out, prevent other instances from doing the same
      // they'll have to wait until this instance completes the fetch, then sets a global
      // while they are in a setTimeout loop
      window.__REACT_WORLD_PRICE_FETCHING__ = true;
      fetch(`https://api.fixer.io/latest?base=${base}`)
        .then(response => response.json())
        .then(data => {
          ls(dateKey, now);
          ls(rateKey, data.rates);
          window.__REACT_WORLD_PRICE_FETCHING__ = false;
          this.setState({ rates: data.rates });
        })
        .catch(() => {
          window.__REACT_WORLD_PRICE_FETCHING__ = false;
        });
    }
  }
 
  get amount(): number {
    const converted = parseFloat(this.props.amount) * (this.state.rates[this.props.displayCurrency] || 1);
    return this.props.hideCents ? this.props.rounding(converted) : converted;
  }
 
  render() {
    const { baseCurrency, displayCurrency, rounding, unwrap } = this.props;
    const amount = parseFloat(this.props.amount);
 
    // check for validity
    const invalidAmount = amount.toString() === 'NaN';
    const invalidBaseCurrency = !(baseCurrency in CURRENCY);
    const invalidDisplayCurrency = !(displayCurrency in CURRENCY);
    Iif (invalidAmount || invalidBaseCurrency || invalidDisplayCurrency) {
      console.error(NAMESPACE, this.props);
      return unwrap ? this.props.amount : <span>{this.props.amount}</span>;
    }
 
    const original = `${formatAmountForCurrency(amount, CURRENCY[baseCurrency], rounding)} ${baseCurrency}`;
    const converted = formatAmountForCurrency(this.amount, CURRENCY[displayCurrency], rounding);
 
    Iif (unwrap) return converted;
    return <span title={original}>{converted}</span>;
  }
}