From 2482a70da1882853cd3348dc1137b72aee4315c2 Mon Sep 17 00:00:00 2001 From: simkasss Date: Mon, 31 Jul 2023 11:59:09 +0300 Subject: [PATCH 1/2] extract component --- src/features/vrf/v2/components/Dropdown.tsx | 137 ++++++++++++++++++ .../vrf/v2/components/DropdownMenu.tsx | 130 ++--------------- 2 files changed, 146 insertions(+), 121 deletions(-) create mode 100644 src/features/vrf/v2/components/Dropdown.tsx diff --git a/src/features/vrf/v2/components/Dropdown.tsx b/src/features/vrf/v2/components/Dropdown.tsx new file mode 100644 index 00000000000..b208b72801f --- /dev/null +++ b/src/features/vrf/v2/components/Dropdown.tsx @@ -0,0 +1,137 @@ +import { useEffect, useMemo, useRef, useState } from "preact/hooks" +import "./dropdown.css" +import { vrfChain, network } from "~/features/vrf/v2/data" +import { RefObject } from "preact" + +interface Props { + placeholder?: string + options: vrfChain[] + setSelectedMainChain + setSelectedChain + setSelectNet +} + +export const Dropdown = ({ placeholder, options, setSelectedMainChain, setSelectedChain, setSelectNet }: Props) => { + const [searchValue, setSearchValue] = useState("") + const [showMenu, setShowMenu] = useState(false) + const [showSubMenu, setShowSubMenu] = useState(-1) + const wrapperRef = useRef(null) + + const useOutsideAlerter = (ref: RefObject) => { + useEffect(() => { + /** + * quit menu if click outside of dropdown + */ + function handleClickOutside(event: MouseEvent) { + if (ref.current && event.target instanceof Node && !ref.current.contains(event.target)) { + setShowMenu(false) + setShowSubMenu(-1) + } + } + // Bind the event listener + document.addEventListener("mousedown", handleClickOutside) + + return () => { + // Unbind the event listener on clean up + document.removeEventListener("mousedown", handleClickOutside) + } + }, [ref]) + } + + const matchingOptions: vrfChain[] = useMemo(() => { + const formattedSearchValue = searchValue.replaceAll(" ", "") + const splittedSearchValueArr = formattedSearchValue.split("-") + if (splittedSearchValueArr.length >= 2) { + return options + } + return options.filter((chain: vrfChain) => { + return searchValue === "" ? chain : chain.name.toLowerCase().includes(searchValue.toLowerCase()) + }) + }, [searchValue, options]) + + const handleSelectedChain = (net: network, chain: vrfChain) => { + let res: string = chain.name + " - " + if (chain.name === net.name) { + res += net.type + } else { + res += net.name + } + + setSelectNet(res) + setSearchValue(res) + } + + const handleInputChange = (event: Event) => { + const { target } = event + if (target instanceof HTMLInputElement) { + const inputValue = target.value + setSearchValue(inputValue) + } + } + + useOutsideAlerter(wrapperRef) + + return ( +
+
+
+ { + setShowMenu(true) + }} + onInput={handleInputChange} + /> + Caret icon +
+
+
+
+ {matchingOptions && + matchingOptions.map((item: vrfChain, index: number) => ( +
+
{ + setShowSubMenu(index) + }} + > + + {item.name} +
+ +
+ ))} +
+
+
+ ) +} diff --git a/src/features/vrf/v2/components/DropdownMenu.tsx b/src/features/vrf/v2/components/DropdownMenu.tsx index 3a67545b7cf..1754e9c1eb4 100644 --- a/src/features/vrf/v2/components/DropdownMenu.tsx +++ b/src/features/vrf/v2/components/DropdownMenu.tsx @@ -1,8 +1,8 @@ -import { useEffect, useMemo, useRef, useState } from "preact/hooks" +import { useState } from "preact/hooks" import "./dropdown.css" import { CostTable } from "./CostTable" +import { Dropdown } from "./Dropdown" import { vrfChain, network } from "~/features/vrf/v2/data" -import { RefObject } from "preact" interface Props { placeholder?: string @@ -14,128 +14,16 @@ export const DropDownMenu = ({ placeholder = "Select a network...", options, met const [selectedMainChain, setSelectedMainChain] = useState(null) const [selectedChain, setSelectedChain] = useState(null) const [selectedNet, setSelectNet] = useState(placeholder) - const [searchValue, setSearchValue] = useState("") - const [showMenu, setShowMenu] = useState(false) - const [showSubMenu, setShowSubMenu] = useState(-1) - const wrapperRef = useRef(null) - - const useOutsideAlerter = (ref: RefObject) => { - useEffect(() => { - /** - * quit menu if click outside of dropdown - */ - function handleClickOutside(event: MouseEvent) { - if (ref.current && event.target instanceof Node && !ref.current.contains(event.target)) { - setShowMenu(false) - setShowSubMenu(-1) - } - } - // Bind the event listener - document.addEventListener("mousedown", handleClickOutside) - - return () => { - // Unbind the event listener on clean up - document.removeEventListener("mousedown", handleClickOutside) - } - }, [ref]) - } - - const matchingOptions: vrfChain[] = useMemo(() => { - const formattedSearchValue = searchValue.replaceAll(" ", "") - const splittedSearchValueArr = formattedSearchValue.split("-") - if (splittedSearchValueArr.length >= 2) { - return options - } - return options.filter((chain: vrfChain) => { - return searchValue === "" ? chain : chain.name.toLowerCase().includes(searchValue.toLowerCase()) - }) - }, [searchValue, options]) - - const handleSelectedChain = (net: network, chain: vrfChain) => { - let res: string = chain.name + " - " - if (chain.name === net.name) { - res += net.type - } else { - res += net.name - } - - setSelectNet(res) - setSearchValue(res) - } - - const handleInputChange = (event: Event) => { - const { target } = event - if (target instanceof HTMLInputElement) { - const inputValue = target.value - setSearchValue(inputValue) - } - } - - useOutsideAlerter(wrapperRef) return (
-
-
-
- { - setShowMenu(true) - }} - onInput={handleInputChange} - /> - Caret icon -
-
-
-
- {matchingOptions && - matchingOptions.map((item: vrfChain, index: number) => ( -
-
{ - setShowSubMenu(index) - }} - > - - {item.name} -
- -
- ))} -
-
-
+ {selectedNet !== placeholder && }
) From d0a0c8bc067ac49e8503a55922585e97c53512bd Mon Sep 17 00:00:00 2001 From: simkasss Date: Mon, 31 Jul 2023 17:25:13 +0300 Subject: [PATCH 2/2] wip --- src/features/vrf/v2/components/CostTable.tsx | 32 ++++++++++++---- src/features/vrf/v2/components/Dropdown.tsx | 11 ++---- .../vrf/v2/components/DropdownMenu.tsx | 17 ++------- src/hooks/useQueryString.ts | 37 ++++++++++++++----- 4 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/features/vrf/v2/components/CostTable.tsx b/src/features/vrf/v2/components/CostTable.tsx index c88b0d53fb4..a1c10402bec 100644 --- a/src/features/vrf/v2/components/CostTable.tsx +++ b/src/features/vrf/v2/components/CostTable.tsx @@ -1,13 +1,13 @@ import { network, vrfChain } from "~/features/vrf/v2/data" import "./costTable.css" -import { useEffect, useReducer } from "preact/hooks" +import { useEffect, useReducer, useState } from "preact/hooks" import { BigNumber, utils } from "ethers" import button from "@chainlink/design-system/button.module.css" +import useQueryString from "~/hooks/useQueryString" interface Props { - mainChain: vrfChain - chain: network method: "subscription" | "directFunding" + options: vrfChain[] } interface directFundingResponse { @@ -139,8 +139,11 @@ const reducer = (state: State, action: Action) => { const cache: Cache = {} -export const CostTable = ({ mainChain, chain, method }: Props) => { +export const CostTable = ({ options, method }: Props) => { const [state, dispatch] = useReducer(reducer, initialState) + const [mainChain, setMainChain] = useState(null) + const [chain, setChain] = useState(null) + const [network] = useQueryString("network") const getDataResponse = async (mainChainName: string, networkName: string): Promise => { const cacheKey = `${mainChainName}-${networkName === mainChainName ? chain.type : networkName}-${method}` @@ -164,11 +167,23 @@ export const CostTable = ({ mainChain, chain, method }: Props) => { } useEffect(() => { - const mainChainName = + let mainChainName, networkName + if (typeof network === "string") { + mainChainName = network.split("-")[0] + networkName = network.split("-")[1] + const newMainChain = options.filter((chain) => chain.name.toLowerCase() === mainChainName)[0] + setMainChain(newMainChain) + const newChain = newMainChain.nets.filter((network) => network.name.toLowerCase() === networkName)[0] + setChain(newChain) + } + if (!mainChain || !chain) { + return + } + mainChainName = mainChain.name === "BNB Chain" ? mainChain.name.replace("Chain", "").replace(" ", "").toLowerCase() : mainChain.name.toLowerCase() - const networkName = + networkName = chain.name === "BNB Chain" ? chain.name.replace("Chain", "").replace(" ", "").toLowerCase() : chain.name.toLowerCase() @@ -221,7 +236,7 @@ export const CostTable = ({ mainChain, chain, method }: Props) => { }) return () => dispatch({ type: "SET_LOADING", payload: false }) - }, [method, mainChain, chain]) + }, [method, network]) const handleRadioChange = (event) => { dispatch({ type: "SET_CURRENT_GAS_LANE", payload: parseInt(event.target.value) }) @@ -471,6 +486,9 @@ export const CostTable = ({ mainChain, chain, method }: Props) => { return res } + if (!mainChain || !chain) { + return + } if (state.isLoading) { return

Data is being fetched. Please wait a moment...

} else { diff --git a/src/features/vrf/v2/components/Dropdown.tsx b/src/features/vrf/v2/components/Dropdown.tsx index b208b72801f..a23e28080b2 100644 --- a/src/features/vrf/v2/components/Dropdown.tsx +++ b/src/features/vrf/v2/components/Dropdown.tsx @@ -2,16 +2,15 @@ import { useEffect, useMemo, useRef, useState } from "preact/hooks" import "./dropdown.css" import { vrfChain, network } from "~/features/vrf/v2/data" import { RefObject } from "preact" +import useQueryString from "~/hooks/useQueryString" interface Props { placeholder?: string options: vrfChain[] - setSelectedMainChain - setSelectedChain - setSelectNet } -export const Dropdown = ({ placeholder, options, setSelectedMainChain, setSelectedChain, setSelectNet }: Props) => { +export const Dropdown = ({ placeholder, options }: Props) => { + const [network, setNetwork] = useQueryString("network") const [searchValue, setSearchValue] = useState("") const [showMenu, setShowMenu] = useState(false) const [showSubMenu, setShowSubMenu] = useState(-1) @@ -57,7 +56,6 @@ export const Dropdown = ({ placeholder, options, setSelectedMainChain, setSelect res += net.name } - setSelectNet(res) setSearchValue(res) } @@ -116,8 +114,7 @@ export const Dropdown = ({ placeholder, options, setSelectedMainChain, setSelect
{ - setSelectedChain(network) - setSelectedMainChain(item) + setNetwork(`${item.name.toLowerCase()}-${network.name.toLowerCase()}`) handleSelectedChain(network, item) setShowMenu(false) setShowSubMenu(-1) diff --git a/src/features/vrf/v2/components/DropdownMenu.tsx b/src/features/vrf/v2/components/DropdownMenu.tsx index 1754e9c1eb4..69c998140ef 100644 --- a/src/features/vrf/v2/components/DropdownMenu.tsx +++ b/src/features/vrf/v2/components/DropdownMenu.tsx @@ -1,8 +1,7 @@ -import { useState } from "preact/hooks" import "./dropdown.css" import { CostTable } from "./CostTable" import { Dropdown } from "./Dropdown" -import { vrfChain, network } from "~/features/vrf/v2/data" +import { vrfChain } from "~/features/vrf/v2/data" interface Props { placeholder?: string @@ -11,20 +10,10 @@ interface Props { } export const DropDownMenu = ({ placeholder = "Select a network...", options, method }: Props) => { - const [selectedMainChain, setSelectedMainChain] = useState(null) - const [selectedChain, setSelectedChain] = useState(null) - const [selectedNet, setSelectNet] = useState(placeholder) - return (
- - {selectedNet !== placeholder && } + +
) } diff --git a/src/hooks/useQueryString.ts b/src/hooks/useQueryString.ts index bb8ad36d456..fecad69e9d1 100644 --- a/src/hooks/useQueryString.ts +++ b/src/hooks/useQueryString.ts @@ -1,4 +1,4 @@ -import { useState, useCallback } from "preact/hooks" +import { useState, useCallback, useEffect } from "preact/hooks" import qs from "query-string" const setQueryStringWithoutPageReload = (qsValue) => { @@ -8,28 +8,45 @@ const setQueryStringWithoutPageReload = (qsValue) => { window.history.replaceState({ path: newurl }, "", newurl) } -const setQueryStringValue = (key, value, queryString = window.location.search) => { +const setQueryStringValue = (searchParamKey, value, queryString = window.location.search) => { if (!window) return const values = qs.parse(queryString) - const newQsValue = qs.stringify({ ...values, [key]: value }) + const newQsValue = qs.stringify({ ...values, [searchParamKey]: value }) setQueryStringWithoutPageReload(`?${newQsValue}`) } -const getQueryStringValue = (key) => { +const getQueryStringValue = (searchParamKey) => { if (typeof window === "undefined") return const values = qs.parse(window.location.search) - return values[key] + return values[searchParamKey] } -function useQueryString(key, initialValue) { - const [value, setValue] = useState(getQueryStringValue(key) || initialValue) + +type SearchParamValue = string | string[] + +function useQueryString( + searchParamKey: string, + initialValue?: SearchParamValue +): [SearchParamValue, (newValue: SearchParamValue) => void] { + const [value, setValue] = useState(getQueryStringValue(searchParamKey) || initialValue) const onSetValue = useCallback( - (newValue) => { + (newValue: string | string[]) => { setValue(newValue) - setQueryStringValue(key, newValue) + setQueryStringValue(searchParamKey, newValue) }, - [key] + [searchParamKey] ) + useEffect(() => { + const body = document.querySelector("body") + const observer = new MutationObserver((mutations) => { + const newQueryStringValue = getQueryStringValue(searchParamKey) + if (newQueryStringValue !== value) { + setValue(newQueryStringValue) + } + }) + observer.observe(body, { childList: true, subtree: true }) + }, []) + return [value, onSetValue] }