|
| 1 | +import { useMemo, useEffect, useCallback } from "react"; |
| 2 | +import { useHistory } from "react-router-dom"; |
| 3 | +import { useGlobalSearch, useQuery } from "src/api/hooks"; |
| 4 | +import { TBaseTag } from "src/api/services"; |
| 5 | +import { setSelectedMarket } from "src/api/store"; |
| 6 | +import { useAppDispatch, useAppSelector } from "src/api/store/hooks"; |
| 7 | +import { TCoin } from "src/api/types"; |
| 8 | +import { TMarketMeta } from "src/components/market/types"; |
| 9 | + |
| 10 | +interface UseSelectedCoinReturn { |
| 11 | + selectedCoin: TCoin | undefined; |
| 12 | + handleSelectedCoin: (market: TMarketMeta) => void; |
| 13 | +} |
| 14 | + |
| 15 | +export const useSelectedCoin = ( |
| 16 | + widgetHash: string, |
| 17 | + coinsData: TCoin[], |
| 18 | + pinnedCoins: TCoin[], |
| 19 | + tags?: TBaseTag[] |
| 20 | +): UseSelectedCoinReturn => { |
| 21 | + const dispatch = useAppDispatch(); |
| 22 | + const history = useHistory(); |
| 23 | + const { lastSelectedKeyword } = useGlobalSearch(); |
| 24 | + const query = useQuery(); |
| 25 | + const coinSlugFromUrl = query.get("coin"); |
| 26 | + |
| 27 | + const prevSelectedMarketData = useAppSelector( |
| 28 | + (state) => state.widgets.market?.[widgetHash] |
| 29 | + ); |
| 30 | + |
| 31 | + // Helper function to remove coin parameter from URL |
| 32 | + const removeCoinFromUrl = useCallback(() => { |
| 33 | + const currentParams = new URLSearchParams(window.location.search); |
| 34 | + currentParams.delete("coin"); |
| 35 | + const newSearch = currentParams.toString(); |
| 36 | + const newUrl = `${window.location.pathname}${newSearch ? `?${newSearch}` : ""}`; |
| 37 | + history.replace(newUrl); |
| 38 | + }, [history]); |
| 39 | + |
| 40 | + const selectedCoin: TCoin | undefined = useMemo(() => { |
| 41 | + // Priority 1: URL parameter - find coin by slug |
| 42 | + if (coinSlugFromUrl) { |
| 43 | + const coinFromUrl = [...pinnedCoins, ...coinsData].find( |
| 44 | + (c) => c.slug === coinSlugFromUrl |
| 45 | + ); |
| 46 | + if (coinFromUrl) { |
| 47 | + return coinFromUrl; |
| 48 | + } |
| 49 | + // Clean up URL if coin slug is not found in available coins |
| 50 | + if (coinsData.length > 0 || pinnedCoins.length > 0) { |
| 51 | + removeCoinFromUrl(); |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + // Priority 2: Redux stored selection |
| 56 | + const storedMarket = [...pinnedCoins, ...coinsData].find( |
| 57 | + (c) => c.id === prevSelectedMarketData?.selectedMarket?.id |
| 58 | + ); |
| 59 | + |
| 60 | + // Priority 3: Fallback to first pinned coin or first available coin |
| 61 | + return storedMarket ?? pinnedCoins[0] ?? coinsData[0] ?? undefined; |
| 62 | + }, [ |
| 63 | + coinSlugFromUrl, |
| 64 | + prevSelectedMarketData?.selectedMarket, |
| 65 | + coinsData, |
| 66 | + pinnedCoins, |
| 67 | + removeCoinFromUrl, |
| 68 | + ]); |
| 69 | + |
| 70 | + const handleSelectedCoin = useCallback( |
| 71 | + (market: TMarketMeta) => { |
| 72 | + dispatch(setSelectedMarket({ widgetHash, market })); |
| 73 | + |
| 74 | + // Remove coin URL parameter when user manually selects a coin |
| 75 | + if (coinSlugFromUrl) { |
| 76 | + removeCoinFromUrl(); |
| 77 | + } |
| 78 | + }, |
| 79 | + [dispatch, widgetHash, coinSlugFromUrl, removeCoinFromUrl] |
| 80 | + ); |
| 81 | + |
| 82 | + // Sync URL parameter to Redux when coin from URL is found |
| 83 | + useEffect(() => { |
| 84 | + // Add guard for data availability |
| 85 | + if (!coinsData.length && !pinnedCoins.length) { |
| 86 | + return; // Wait for data to load |
| 87 | + } |
| 88 | + if ( |
| 89 | + coinSlugFromUrl && |
| 90 | + selectedCoin && |
| 91 | + selectedCoin.slug === coinSlugFromUrl |
| 92 | + ) { |
| 93 | + // Only update Redux if the current Redux state doesn't match the URL selection |
| 94 | + if ( |
| 95 | + selectedCoin.id !== prevSelectedMarketData?.selectedMarket?.id |
| 96 | + ) { |
| 97 | + handleSelectedCoin(selectedCoin); |
| 98 | + } |
| 99 | + } |
| 100 | + }, [ |
| 101 | + coinSlugFromUrl, |
| 102 | + selectedCoin, |
| 103 | + prevSelectedMarketData?.selectedMarket?.id, |
| 104 | + handleSelectedCoin, |
| 105 | + coinsData.length, |
| 106 | + pinnedCoins.length, |
| 107 | + ]); |
| 108 | + |
| 109 | + // Existing useEffect for global search |
| 110 | + useEffect(() => { |
| 111 | + if ( |
| 112 | + lastSelectedKeyword && |
| 113 | + tags?.find((t) => t.id === lastSelectedKeyword.tag.id) |
| 114 | + ) { |
| 115 | + const newMarketFromSearch = coinsData.find((marketMeta) => { |
| 116 | + return marketMeta.tags?.find( |
| 117 | + (t) => t.id === lastSelectedKeyword.tag.id |
| 118 | + ); |
| 119 | + }); |
| 120 | + if (newMarketFromSearch) { |
| 121 | + handleSelectedCoin(newMarketFromSearch); |
| 122 | + } |
| 123 | + } |
| 124 | + }, [lastSelectedKeyword, coinsData, tags, handleSelectedCoin]); |
| 125 | + |
| 126 | + return { |
| 127 | + selectedCoin, |
| 128 | + handleSelectedCoin, |
| 129 | + }; |
| 130 | +}; |
0 commit comments