MyCrypto/common/components/BalanceSidebar/TokenBalances/AddCustomTokenForm/FieldInput.tsx

135 lines
3.8 KiB
TypeScript

import React from 'react';
import { Result } from 'mycrypto-nano-result';
import { shepherdProvider } from 'libs/nodes';
import { Input } from 'components/ui';
import Spinner from 'components/ui/Spinner';
import ERC20 from 'libs/erc20';
interface OwnProps {
fieldToFetch: keyof Pick<typeof ERC20, 'symbol' | 'decimals'>;
fieldName: string;
address?: string;
userInputValidator(input: string): Result<string>;
fetchedFieldValidator?(input: any): Result<string>;
shouldEnableAutoField(input: Result<string>): boolean;
onChange(symbol: Result<string>): void;
}
interface State {
field: Result<string>;
autoField: boolean;
userInput: string;
addressToLoad?: string;
loading: boolean;
}
export class FieldInput extends React.Component<OwnProps, State> {
public static getDerivedStateFromProps(
nextProps: OwnProps,
prevState: State
): Partial<State> | null {
if (nextProps.address && nextProps.address !== prevState.addressToLoad) {
return { loading: true, autoField: true, addressToLoad: nextProps.address };
}
return null;
}
public state: State = {
userInput: '',
autoField: true,
field: Result.from({ res: '' }),
loading: false
};
private currentRequest: Promise<any> | null;
public componentDidUpdate() {
if (this.state.addressToLoad && this.state.loading) {
this.attemptToLoadField(this.state.addressToLoad);
}
}
public componentWillUnmount() {
if (this.currentRequest) {
this.currentRequest = null;
}
}
public render() {
const { userInput, field, autoField, loading } = this.state;
return (
<label className="AddCustom-field form-group">
<div className="input-group-header">{this.props.fieldName}</div>
{loading ? (
<Spinner />
) : (
<Input
isValid={field.ok()}
className="input-group-input-small"
type="text"
name={this.props.fieldName}
readOnly={autoField}
value={field.ok() ? field.unwrap() : userInput}
onChange={this.handleFieldChange}
/>
)}
{field.err() && <div className="AddCustom-field-error">{field.err()}</div>}
</label>
);
}
private handleFieldChange = (args: React.FormEvent<HTMLInputElement>) => {
const userInput = args.currentTarget.value;
const field = this.props.userInputValidator(userInput);
this.setState({ userInput, field });
this.props.onChange(field);
};
private attemptToLoadField(address: string) {
// process request
this.currentRequest = this.loadField(address)
// set state on successful request e.g it was not cancelled
// and then also set our current request to null
.then(({ [this.props.fieldToFetch]: field }) =>
this.setState({
field,
loading: false,
autoField: this.props.shouldEnableAutoField(field)
})
)
.catch(e => {
console.error(e);
// if the component is unmounted, then dont call set state
if (!this.currentRequest) {
return;
}
// otherwise it was a failed fetch call
this.setState({ autoField: false, loading: false });
})
.then(() => (this.currentRequest = null));
}
private loadField(address: string) {
const { fieldToFetch } = this.props;
return shepherdProvider
.sendCallRequest({ data: ERC20[fieldToFetch].encodeInput(), to: address })
.then(ERC20[fieldToFetch].decodeOutput as any)
.then(({ [fieldToFetch]: field }) => {
let result: Result<string>;
if (this.props.fetchedFieldValidator) {
result = this.props.fetchedFieldValidator(field);
} else {
result = Result.from({ res: field });
}
//
//
this.props.onChange(result);
return { [fieldToFetch]: result };
});
}
}