BalanceField.tsx
  1  import React from 'react';
  2  import { connect } from 'react-redux';
  3  import { Result } from 'mycrypto-nano-result';
  4  
  5  import ERC20 from 'libs/erc20';
  6  import { shepherdProvider } from 'libs/nodes';
  7  import { AppState } from 'features/reducers';
  8  import { walletSelectors } from 'features/wallet';
  9  import Spinner from 'components/ui/Spinner';
 10  import { Input } from 'components/ui';
 11  
 12  interface OwnProps {
 13    address?: string;
 14  }
 15  
 16  interface StateProps {
 17    walletInst: ReturnType<typeof walletSelectors.getWalletInst>;
 18  }
 19  
 20  interface State {
 21    balance: Result<string>;
 22    addressToLoad?: string;
 23    loading: boolean;
 24  }
 25  
 26  type Props = OwnProps & StateProps;
 27  
 28  class BalanceFieldClass extends React.Component<Props, State> {
 29    public static getDerivedStateFromProps(
 30      nextProps: OwnProps,
 31      prevState: State
 32    ): Partial<State> | null {
 33      if (nextProps.address && nextProps.address !== prevState.addressToLoad) {
 34        return { loading: true, addressToLoad: nextProps.address };
 35      }
 36      return null;
 37    }
 38  
 39    public state: State = {
 40      balance: Result.from({ res: '' }),
 41      loading: false
 42    };
 43  
 44    private currentRequest: Promise<any> | null;
 45  
 46    public componentDidUpdate() {
 47      if (this.state.addressToLoad && this.state.loading) {
 48        this.attemptToLoadBalance(this.state.addressToLoad);
 49      }
 50    }
 51  
 52    public componentWillUnmount() {
 53      if (this.currentRequest) {
 54        this.currentRequest = null;
 55      }
 56    }
 57    public render() {
 58      const { balance, loading } = this.state;
 59  
 60      return (
 61        <label className="AddCustom-field form-group">
 62          <div className="input-group-header">Balance</div>
 63          {loading ? (
 64            <Spinner />
 65          ) : (
 66            <Input
 67              isValid={balance.ok()}
 68              className="input-group-input-small"
 69              type="text"
 70              name="Balance"
 71              readOnly={true}
 72              value={balance.ok() ? balance.unwrap() : '0'}
 73            />
 74          )}
 75          {balance.err() && <div className="AddCustom-field-error">{balance.err()}</div>}
 76        </label>
 77      );
 78    }
 79  
 80    private attemptToLoadBalance(address: string) {
 81      // process request
 82      this.currentRequest = this.loadBalance(address)
 83        // set state on successful request e.g it was not cancelled
 84        // and then also set our current request to null
 85        .then(({ balance }) =>
 86          this.setState({
 87            balance,
 88            loading: false
 89          })
 90        )
 91        .catch(e => {
 92          console.error(e);
 93          // if the component is unmounted, then dont call set state
 94          if (!this.currentRequest) {
 95            return;
 96          }
 97  
 98          // otherwise it was a failed fetch call
 99          this.setState({ loading: false });
100        })
101        .then(() => (this.currentRequest = null));
102    }
103  
104    private loadBalance(address: string) {
105      if (!this.props.walletInst) {
106        return Promise.reject('No wallet found');
107      }
108  
109      const owner = this.props.walletInst.getAddressString();
110      return shepherdProvider
111        .sendCallRequest({ data: ERC20.balanceOf.encodeInput({ _owner: owner }), to: address })
112        .then(ERC20.balanceOf.decodeOutput)
113        .then(({ balance }) => {
114          const result = Result.from({ res: balance });
115          return { balance: result };
116        });
117    }
118  }
119  
120  function mapStateToProps(state: AppState): StateProps {
121    return { walletInst: walletSelectors.getWalletInst(state) };
122  }
123  
124  export const BalanceField = connect(mapStateToProps)(BalanceFieldClass);