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);