I am creating a screen to "buy" crypto, and I have organized the components as in the image attached. When I change the EUR input amount I expect the calculations to be done very fast, since no API calls are involved (just basic math operations). Since I am passing the useEffect state and update function down from BuyScreen to CurrencyWidget and FeeSummary as props, I need to use (as far as I know) useEffect and a dependency on a value to make sure I have the last updated one.
The issue consists in how slow the UI is updating (see GIF), since I am changing several useEffect hooks to be sure I have all the updated values. Is there something that can be improved in my code to solve this issue?
BuyScreen.js
const calculateTotalBuyTransactionAmount = (
buySourceAmount,
cryptoBuyPrice
) => {
return (buySourceAmount / cryptoBuyPrice).toFixed(8);
};
const calculateSubtotal = (amount, fees) => {
return (amount - fees).toFixed(2);
};
const BuyScreen = (props) => {
const [buySourceAmount, setBuySourceAmount] = useState(0.0);
const [buyDestinationAmount, setBuyDestinationAmount] = useState(0.0);
const [feeAmount, setFeeAmount] = useState(0.0);
const [feeSubTotalAmount, setFeeSubTotalAmount] = useState(0.0);
const [cryptoBuyPrice, setCryptoBuyPrice] = useState(
pricesMock[0].values.prices.EUR.buy
);
useEffect(() => {
setFeeAmount((buySourceAmount * 0.05).toFixed(2));
}, [buySourceAmount]);
useEffect(() => {
setFeeSubTotalAmount(calculateSubtotal(buySourceAmount, feeAmount));
}, [feeAmount]);
useEffect(() => {
setBuyDestinationAmount(
calculateTotalBuyTransactionAmount(feeSubTotalAmount, cryptoBuyPrice)
);
}, [feeSubTotalAmount]);
console.log("Rendering BuyScreen");
return (
<SafeAreaView style={{ flex: 1 }}>
<Container>
<Content padder>
<View>
<CurrencyWidget
currencyName={balancesMock.values.currencyBalances[0].name}
currencyCode={balancesMock.values.currencyBalances[0].code}
balance={balancesMock.values.currencyBalances[0].total}
inputAmount={buySourceAmount}
setInputAmount={(amount) => {
setBuySourceAmount(amount);
}}
autofocus
/>
<FeesSummary
feeAmount={feeAmount}
feeSubTotalAmount={feeSubTotalAmount}
/>
<RealTimeCryptoPriceWidget
currencyCode={balancesMock.values.cryptoBalances[0].code}
cryptoBuyPrice={cryptoBuyPrice}
/>
<CurrencyWidget
currencyName={balancesMock.values.cryptoBalances[0].name}
currencyCode={balancesMock.values.cryptoBalances[0].code}
balance={balancesMock.values.cryptoBalances[0].total}
inputAmount={buyDestinationAmount}
destinationCurrency
/>
</View>
<View>
<LoadingSpinner area="buy-button">
<Button
block
primary-light
style={{
marginBottom: 16,
}}
>
<Text>Buy {balancesMock.values.cryptoBalances[0].name}</Text>
</Button>
</LoadingSpinner>
</View>
</Content>
</Container>
</SafeAreaView>
);
};
CurrencyWidget.js
const CurrencyWidget = (props) => {
console.log("Rendering CurrencyWidget");
return (
<Grid>
<Col>
<Text notification-light>
{props.currencyName} ({props.currencyCode})
</Text>
<Text label-light style={styles.balanceAmount}>
Balance: {props.currencyCode} {props.balance}
</Text>
</Col>
<Col style={{ alignItems: "flex-end", justifyContent: "flex-start" }}>
<Item regular>
<Input
transactionAmount
keyboardType="numeric"
placeholder="0"
editable={!props.destinationCurrency}
autoFocus={props.autofocus}
value={String(props.inputAmount)}
onChangeText={(amount) => {
console.log("amount: ", amount);
amount === ""
? props.setInputAmount("0")
: props.setInputAmount(amount);
}}
/>
</Item>
</Col>
</Grid>
);
};
FeeSummary.js
const FeesSummary = props => {
const sellPageFees = () => {
return (
<Grid style={styles.verticalPadding}>
<Row>
<Col style={styles.verticalCenter}>
<Text label-light>Subtotal</Text>
</Col>
<Col style={[styles.verticalCenter, { alignItems: "flex-end" }]}>
<Text notification-light-regular>
EUR {props.feeSubTotalAmount}
</Text>
</Col>
</Row>
<Row>
<Col style={styles.verticalCenter}>
<Text label-light>Fees</Text>
</Col>
<Col style={[styles.verticalCenter, { alignItems: "flex-end" }]}>
<Text notification-light-regular>EUR {props.feeAmount}</Text>
</Col>
</Row>
</Grid>
);
};
const buyPageFees = () => {
return (
<Grid style={styles.verticalPadding}>
<Row>
<Col style={styles.verticalCenter}>
<Text label-light>Fees</Text>
</Col>
<Col style={[styles.verticalCenter, { alignItems: "flex-end" }]}>
<Text notification-light-regular>EUR {props.feeAmount}</Text>
</Col>
</Row>
<Row>
<Col style={styles.verticalCenter}>
<Text label-light>Subtotal</Text>
</Col>
<Col style={[styles.verticalCenter, { alignItems: "flex-end" }]}>
<Text notification-light-regular>
EUR {props.feeSubTotalAmount}
</Text>
</Col>
</Row>
</Grid>
);
};
console.log("Rendering FeeSummary");
return props.isSellPage ? sellPageFees() : buyPageFees();
};
For anyone running in the same issue, I managed to solve by handling the state update separately from the calculations needed to update the fields, see the following code:
BuyScreen.js
<CurrencyWidget
currencyName={balancesMock.values.currencyBalances[0].name}
currencyCode={balancesMock.values.currencyBalances[0].code}
balance={balancesMock.values.currencyBalances[0].total}
inputAmount={buySourceAmount}
setInputAmount={handleBuySourceAmount}
autofocus
/>
And the handleBuySourceAmount function:
const handleBuySourceAmount = (amount) => {
const _feeAmount = calculateFees(amount);
const _subTotalAmount = calculateSubtotal(amount, _feeAmount);
const _totalBuyAmount = calculateTotalBuyTransactionAmount(
_subTotalAmount,
cryptoBuyPrice
);
setBuySourceAmount(amount);
setFeeAmount(_feeAmount);
setFeeSubTotalAmount(_subTotalAmount);
setBuyDestinationAmount(_totalBuyAmount);
};