From 1fed25ea83da753357d711276ff702de2b7ad8e9 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Nov 2021 09:56:53 +1100 Subject: [PATCH 1/9] Rewrite Trade as `function` component This allows us to co-locate the `default` export with the component definition. --- taker-frontend/src/components/Trade.tsx | 37 ++++++++++++------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/taker-frontend/src/components/Trade.tsx b/taker-frontend/src/components/Trade.tsx index fa85f0e..1ce5f32 100644 --- a/taker-frontend/src/components/Trade.tsx +++ b/taker-frontend/src/components/Trade.tsx @@ -79,24 +79,22 @@ function AlertBox({ title, description }: AlertBoxProps) { ); } -const Trade = ( - { - connectedToMaker, - minQuantity, - maxQuantity, - referencePrice: referencePriceAsNumber, - askPrice: askPriceAsNumber, - quantity, - onQuantityChange, - margin: marginAsNumber, - leverage, - liquidationPrice: liquidationPriceAsNumber, - onLongSubmit, - isLongSubmitting, - orderId, - walletBalance, - }: TradeProps, -) => { +export default function Trade({ + connectedToMaker, + minQuantity, + maxQuantity, + referencePrice: referencePriceAsNumber, + askPrice: askPriceAsNumber, + quantity, + onQuantityChange, + margin: marginAsNumber, + leverage, + liquidationPrice: liquidationPriceAsNumber, + onLongSubmit, + isLongSubmitting, + orderId, + walletBalance, +}: TradeProps) { let outerCircleBg = useColorModeValue("gray.100", "gray.700"); let innerCircleBg = useColorModeValue("gray.200", "gray.600"); @@ -271,8 +269,7 @@ const Trade = ( ); -}; -export default Trade; +} interface QuantityProps { min: number; From e7ec622672ee254df525957728a49d411b73cf44 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Nov 2021 10:13:58 +1100 Subject: [PATCH 2/9] Margin is not actually optional The type definition was wrong. Margin is actually always set. --- taker-frontend/src/components/Trade.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taker-frontend/src/components/Trade.tsx b/taker-frontend/src/components/Trade.tsx index 1ce5f32..0bcae3c 100644 --- a/taker-frontend/src/components/Trade.tsx +++ b/taker-frontend/src/components/Trade.tsx @@ -56,7 +56,7 @@ interface TradeProps { maxQuantity: number; referencePrice?: number; askPrice?: number; - margin?: string; + margin: string; leverage?: number; quantity: string; liquidationPrice?: number; @@ -101,7 +101,7 @@ export default function Trade({ const referencePrice = `$${referencePriceAsNumber?.toLocaleString() || "0.0"}`; const askPrice = `$${askPriceAsNumber?.toLocaleString() || "0.0"}`; const liquidationPrice = `$${liquidationPriceAsNumber?.toLocaleString() || "0.0"}`; - const margin = `₿${marginAsNumber?.toLocaleString() || "0.0"}`; + const margin = `₿${marginAsNumber.toLocaleString()}`; const { isOpen, onOpen, onClose } = useDisclosure(); From c050d5956f542617ed4f0708bd29ee3c026b40b1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Nov 2021 10:16:39 +1100 Subject: [PATCH 3/9] Store margin as number We are parsing this as a number a million times inside `Trade`. Storing it as a number is a first step in fixing this. --- taker-frontend/src/App.tsx | 4 ++-- taker-frontend/src/components/Trade.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/taker-frontend/src/App.tsx b/taker-frontend/src/App.tsx index 9ae10e3..ec372d6 100644 --- a/taker-frontend/src/App.tsx +++ b/taker-frontend/src/App.tsx @@ -66,7 +66,7 @@ export const App = () => { const connectedToMaker = connectedToMakerOrUndefined ? connectedToMakerOrUndefined! : false; let [quantity, setQuantity] = useState("0"); - let [margin, setMargin] = useState("0"); + let [margin, setMargin] = useState(0); let [userHasEdited, setUserHasEdited] = useState(false); const { price: askPrice, min_quantity, max_quantity, leverage, liquidation_price: liquidationPrice } = order || {}; @@ -76,7 +76,7 @@ export const App = () => { let [calculateMargin] = usePostRequest( "/api/calculate/margin", (response) => { - setMargin(response.margin.toString()); + setMargin(response.margin); }, ); let [makeNewOrderRequest, isCreatingNewOrderRequest] = usePostRequest("/api/cfd/order"); diff --git a/taker-frontend/src/components/Trade.tsx b/taker-frontend/src/components/Trade.tsx index 0bcae3c..8f9394d 100644 --- a/taker-frontend/src/components/Trade.tsx +++ b/taker-frontend/src/components/Trade.tsx @@ -56,7 +56,7 @@ interface TradeProps { maxQuantity: number; referencePrice?: number; askPrice?: number; - margin: string; + margin: number; leverage?: number; quantity: string; liquidationPrice?: number; From 90b8b5d6841a9fec41a1f353fa9c490edd3d1ab0 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Nov 2021 10:19:10 +1100 Subject: [PATCH 4/9] Delay formatting of margin as long as possible --- taker-frontend/src/components/Trade.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/taker-frontend/src/components/Trade.tsx b/taker-frontend/src/components/Trade.tsx index 8f9394d..3e02ad0 100644 --- a/taker-frontend/src/components/Trade.tsx +++ b/taker-frontend/src/components/Trade.tsx @@ -189,7 +189,7 @@ export default function Trade({ - +
@@ -221,12 +221,12 @@ export default function Trade({ - By submitting, {margin} will be locked on-chain in a contract. + By submitting, ₿{marginAsNumber} will be locked on-chain in a contract. - + @@ -327,7 +327,7 @@ function Leverage({ leverage }: LeverageProps) { } interface MarginProps { - margin?: string; + margin: number; } function Margin({ margin }: MarginProps) { @@ -335,7 +335,7 @@ function Margin({ margin }: MarginProps) { Required margin: - {margin} + ₿{margin} The collateral you will need to provide From 024ded6eb425a0a93330b030b2495694a775bef2 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Nov 2021 10:22:49 +1100 Subject: [PATCH 5/9] Don't parse margin if we have it as a number already --- taker-frontend/src/components/Trade.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taker-frontend/src/components/Trade.tsx b/taker-frontend/src/components/Trade.tsx index 3e02ad0..d3f9b93 100644 --- a/taker-frontend/src/components/Trade.tsx +++ b/taker-frontend/src/components/Trade.tsx @@ -107,7 +107,7 @@ export default function Trade({ const parse = (val: any) => Number.parseInt(val.replace(/^\$/, "")); - const balanceTooLow = walletBalance && walletBalance < parse(margin); + const balanceTooLow = walletBalance && walletBalance < marginAsNumber; const quantityTooHigh = maxQuantity < parse(quantity); const quantityTooLow = minQuantity > parse(quantity); const quantityGreaterZero = parse(quantity) > 0; From 7ec718852856abeb14e7c30fdb7b481c79e0cae0 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Nov 2021 10:23:24 +1100 Subject: [PATCH 6/9] Remove unnecessary `toString`ing We no longer need this. --- taker-frontend/src/components/Trade.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/taker-frontend/src/components/Trade.tsx b/taker-frontend/src/components/Trade.tsx index d3f9b93..e16a6c5 100644 --- a/taker-frontend/src/components/Trade.tsx +++ b/taker-frontend/src/components/Trade.tsx @@ -87,7 +87,7 @@ export default function Trade({ askPrice: askPriceAsNumber, quantity, onQuantityChange, - margin: marginAsNumber, + margin, leverage, liquidationPrice: liquidationPriceAsNumber, onLongSubmit, @@ -101,13 +101,12 @@ export default function Trade({ const referencePrice = `$${referencePriceAsNumber?.toLocaleString() || "0.0"}`; const askPrice = `$${askPriceAsNumber?.toLocaleString() || "0.0"}`; const liquidationPrice = `$${liquidationPriceAsNumber?.toLocaleString() || "0.0"}`; - const margin = `₿${marginAsNumber.toLocaleString()}`; const { isOpen, onOpen, onClose } = useDisclosure(); const parse = (val: any) => Number.parseInt(val.replace(/^\$/, "")); - const balanceTooLow = walletBalance && walletBalance < marginAsNumber; + const balanceTooLow = walletBalance && walletBalance < margin; const quantityTooHigh = maxQuantity < parse(quantity); const quantityTooLow = minQuantity > parse(quantity); const quantityGreaterZero = parse(quantity) > 0; @@ -189,7 +188,7 @@ export default function Trade({ - +
@@ -221,12 +220,12 @@ export default function Trade({
Margin{margin}₿{marginAsNumber}
Leverage
- By submitting, ₿{marginAsNumber} will be locked on-chain in a contract. + By submitting, ₿{margin} will be locked on-chain in a contract. - + From c7e91b4eea0cd6530c2761c971699c4d7ef59640 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Nov 2021 10:27:39 +1100 Subject: [PATCH 7/9] Half the number of `calculate` requests We already have a `useEffect` that re-calculates the margin, no need to do it again the onChange handler. --- taker-frontend/src/App.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/taker-frontend/src/App.tsx b/taker-frontend/src/App.tsx index ec372d6..b07ec57 100644 --- a/taker-frontend/src/App.tsx +++ b/taker-frontend/src/App.tsx @@ -128,16 +128,6 @@ export const App = () => { onQuantityChange={(valueString: string) => { setUserHasEdited(true); setQuantity(parse(valueString)); - if (!order) { - return; - } - let quantity = valueString ? Number.parseFloat(valueString) : 0; - let payload: MarginRequestPayload = { - leverage: order.leverage, - price: order.price, - quantity, - }; - calculateMargin(payload); }} onLongSubmit={makeNewOrderRequest} isLongSubmitting={isCreatingNewOrderRequest} From b840bdee89bab38d9e55085cf9472f572f9f54c3 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Nov 2021 11:24:45 +1100 Subject: [PATCH 8/9] Debounce margin calculation We wait for at least 500ms before actually calculating the margin. This avoids unnecessary API requests when the user rapidly edits the input field. --- taker-frontend/src/App.tsx | 36 +++++++++++++----------- taker-frontend/src/useDebouncedEffect.ts | 11 ++++++++ 2 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 taker-frontend/src/useDebouncedEffect.ts diff --git a/taker-frontend/src/App.tsx b/taker-frontend/src/App.tsx index b07ec57..aae240e 100644 --- a/taker-frontend/src/App.tsx +++ b/taker-frontend/src/App.tsx @@ -11,7 +11,7 @@ import { VStack, } from "@chakra-ui/react"; import * as React from "react"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { Route, Routes } from "react-router-dom"; import { useEventSource } from "react-sse-hooks"; import useWebSocket from "react-use-websocket"; @@ -33,6 +33,7 @@ import { StateGroupKey, WalletInfo, } from "./types"; +import useDebouncedEffect from "./useDebouncedEffect"; import useLatestEvent from "./useLatestEvent"; import usePostRequest from "./usePostRequest"; @@ -81,22 +82,23 @@ export const App = () => { ); let [makeNewOrderRequest, isCreatingNewOrderRequest] = usePostRequest("/api/cfd/order"); - useEffect(() => { - if (!order) { - return; - } - let quantity = effectiveQuantity ? Number.parseFloat(effectiveQuantity) : 0; - let payload: MarginRequestPayload = { - leverage: order.leverage, - price: order.price, - quantity, - }; - calculateMargin(payload); - }, // Eslint demands us to include `calculateMargin` in the list of dependencies. - // We don't want that as we will end up in an endless loop. It is safe to ignore `calculateMargin` because - // nothing in `calculateMargin` depends on outside values, i.e. is guaranteed to be stable. - // eslint-disable-next-line react-hooks/exhaustive-deps - [margin, effectiveQuantity, order]); + useDebouncedEffect( + () => { + if (!order) { + return; + } + let quantity = effectiveQuantity ? Number.parseFloat(effectiveQuantity) : 0; + let payload: MarginRequestPayload = { + leverage: order.leverage, + price: order.price, + quantity, + }; + + calculateMargin(payload); + }, + [margin, effectiveQuantity, order], + 500, + ); const format = (val: any) => `$` + val; const parse = (val: any) => val.replace(/^\$/, ""); diff --git a/taker-frontend/src/useDebouncedEffect.ts b/taker-frontend/src/useDebouncedEffect.ts new file mode 100644 index 0000000..0df6886 --- /dev/null +++ b/taker-frontend/src/useDebouncedEffect.ts @@ -0,0 +1,11 @@ +import { useEffect } from "react"; + +// Copied from: https://stackoverflow.com/a/61127960/2489334 +export default function useDebouncedEffect(effect: () => void, deps: any[] | undefined, delay: number) { + return useEffect(() => { + const handler = setTimeout(() => effect(), delay); + + return () => clearTimeout(handler); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [...deps || [], delay]); +} From ec712b33aece2d5683aac7d76938f76392263f35 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Nov 2021 11:38:45 +1100 Subject: [PATCH 9/9] Replace formatting of quantity with input addon --- taker-frontend/src/App.tsx | 7 ++---- taker-frontend/src/components/Trade.tsx | 31 ++++++++++++++----------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/taker-frontend/src/App.tsx b/taker-frontend/src/App.tsx index aae240e..db1803c 100644 --- a/taker-frontend/src/App.tsx +++ b/taker-frontend/src/App.tsx @@ -100,9 +100,6 @@ export const App = () => { 500, ); - const format = (val: any) => `$` + val; - const parse = (val: any) => val.replace(/^\$/, ""); - return ( <>
Margin₿{marginAsNumber}₿{margin}
Leverage