From 4a47cedad8564b722c54cb6c7576d7e2211545ef Mon Sep 17 00:00:00 2001 From: towfiqi Date: Sat, 13 Jan 2024 10:10:49 +0600 Subject: [PATCH] refactor: improves Performance & Code Readability - Replaces useEffet with useMemo & useLayoutEffect where necessary. - Converts some useEffects to separate hooks. - Moves the functions defined within components to utils. - Splits the Keywords renderPosition function to its own component. --- components/common/Modal.tsx | 15 ++----- components/domains/DomainSettings.tsx | 13 +++--- components/insight/InsightItem.tsx | 3 +- components/insight/InsightStats.tsx | 27 +++++------ components/keywords/Keyword.tsx | 20 +++------ components/keywords/KeywordDetails.tsx | 45 ++++--------------- components/keywords/KeywordFilter.tsx | 23 +++------- components/keywords/KeywordPosition.tsx | 19 ++++++++ components/keywords/KeywordsTable.tsx | 17 +++---- components/keywords/SCKeyword.tsx | 15 +++---- components/keywords/SCKeywordsTable.tsx | 23 ++++------ components/settings/Settings.tsx | 15 +------ hooks/useIsMobile.tsx | 12 +++++ hooks/useOnKey.tsx | 17 +++++++ hooks/useWindowResize.tsx | 13 ++++++ services/keywords.tsx | 20 +++++++++ .../client}/generateChartData.ts | 0 utils/client/helpers.ts | 2 + 18 files changed, 146 insertions(+), 153 deletions(-) create mode 100644 components/keywords/KeywordPosition.tsx create mode 100644 hooks/useIsMobile.tsx create mode 100644 hooks/useOnKey.tsx create mode 100644 hooks/useWindowResize.tsx rename {components/common => utils/client}/generateChartData.ts (100%) create mode 100644 utils/client/helpers.ts diff --git a/components/common/Modal.tsx b/components/common/Modal.tsx index e001b7a..1c368d0 100644 --- a/components/common/Modal.tsx +++ b/components/common/Modal.tsx @@ -1,5 +1,6 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import Icon from './Icon'; +import useOnKey from '../../hooks/useOnKey'; type ModalProps = { children: React.ReactNode, @@ -9,17 +10,7 @@ type ModalProps = { } const Modal = ({ children, width = '1/2', closeModal, title }:ModalProps) => { - useEffect(() => { - const closeModalonEsc = (event:KeyboardEvent) => { - if (event.key === 'Escape') { - closeModal(); - } - }; - window.addEventListener('keydown', closeModalonEsc, false); - return () => { - window.removeEventListener('keydown', closeModalonEsc, false); - }; - }, [closeModal]); + useOnKey('Escape', closeModal); const closeOnBGClick = (e:React.SyntheticEvent) => { e.stopPropagation(); diff --git a/components/domains/DomainSettings.tsx b/components/domains/DomainSettings.tsx index 6336891..f28a216 100644 --- a/components/domains/DomainSettings.tsx +++ b/components/domains/DomainSettings.tsx @@ -1,5 +1,5 @@ import { useRouter } from 'next/router'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import Icon from '../common/Icon'; import Modal from '../common/Modal'; import { useDeleteDomain, useUpdateDomain } from '../../services/domains'; @@ -18,7 +18,10 @@ const DomainSettings = ({ domain, closeModal }: DomainSettingsProps) => { const router = useRouter(); const [showRemoveDomain, setShowRemoveDomain] = useState(false); const [settingsError, setSettingsError] = useState({ type: '', msg: '' }); - const [domainSettings, setDomainSettings] = useState({ notification_interval: 'never', notification_emails: '' }); + const [domainSettings, setDomainSettings] = useState(() => ({ + notification_interval: domain && domain.notification_interval ? domain.notification_interval : 'never', + notification_emails: domain && domain.notification_emails ? domain.notification_emails : '', + })); const { mutate: updateMutate } = useUpdateDomain(() => closeModal(false)); const { mutate: deleteMutate } = useDeleteDomain(() => { @@ -26,12 +29,6 @@ const DomainSettings = ({ domain, closeModal }: DomainSettingsProps) => { router.push('/domains'); }); - useEffect(() => { - if (domain) { - setDomainSettings({ notification_interval: domain.notification_interval, notification_emails: domain.notification_emails }); - } - }, [domain]); - const updateNotiEmails = (event:React.FormEvent) => { setDomainSettings({ ...domainSettings, notification_emails: event.currentTarget.value }); }; diff --git a/components/insight/InsightItem.tsx b/components/insight/InsightItem.tsx index abb19a1..cf0cdd1 100644 --- a/components/insight/InsightItem.tsx +++ b/components/insight/InsightItem.tsx @@ -1,6 +1,7 @@ import React from 'react'; import countries from '../../utils/countries'; import Icon from '../common/Icon'; +import { formattedNum } from '../../utils/client/helpers'; type InsightItemProps = { item: SCInsightItem, @@ -16,7 +17,6 @@ const InsightItem = ({ item, lastItem, type, domain }:InsightItemProps) => { firstItem = date && new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }).format(new Date(date)); } if (type === 'countries') { firstItem = countries[country] && countries[country][0]; } - const formattedNum = (num:number) => new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(num); return (
{ {Math.round(position)}
- {/*
{formattedNum(clicks)}
*/}
{formattedNum(clicks)} diff --git a/components/insight/InsightStats.tsx b/components/insight/InsightStats.tsx index 34ae00b..df80535 100644 --- a/components/insight/InsightStats.tsx +++ b/components/insight/InsightStats.tsx @@ -1,6 +1,7 @@ -import React, { useMemo, useState, useEffect } from 'react'; +import React, { useMemo } from 'react'; import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js'; import { Line } from 'react-chartjs-2'; +import { formattedNum } from '../../utils/client/helpers'; ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); @@ -12,21 +13,15 @@ type InsightStatsProps = { } const InsightStats = ({ stats = [], totalKeywords = 0, totalPages = 0 }:InsightStatsProps) => { - const formattedNum = (num:number) => new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(num); - const [totalStat, setTotalStat] = useState({ impressions: 0, clicks: 0, ctr: 0, position: 0 }); - - useEffect(() => { - if (stats.length > 0) { - const totalStats = stats.reduce((acc, item) => { - return { - impressions: item.impressions + acc.impressions, - clicks: item.clicks + acc.clicks, - ctr: item.ctr + acc.ctr, - position: item.position + acc.position, - }; - }, { impressions: 0, clicks: 0, ctr: 0, position: 0 }); - setTotalStat(totalStats); - } + const totalStat = useMemo(() => { + return stats.reduce((acc, item) => { + return { + impressions: item.impressions + acc.impressions, + clicks: item.clicks + acc.clicks, + ctr: item.ctr + acc.ctr, + position: item.position + acc.position, + }; + }, { impressions: 0, clicks: 0, ctr: 0, position: 0 }); }, [stats]); const chartData = useMemo(() => { diff --git a/components/keywords/Keyword.tsx b/components/keywords/Keyword.tsx index 37674a3..5d3500b 100644 --- a/components/keywords/Keyword.tsx +++ b/components/keywords/Keyword.tsx @@ -4,7 +4,8 @@ import dayjs from 'dayjs'; import Icon from '../common/Icon'; import countries from '../../utils/countries'; import ChartSlim from '../common/ChartSlim'; -import { generateTheChartData } from '../common/generateChartData'; +import KeywordPosition from './KeywordPosition'; +import { generateTheChartData } from '../../utils/client/generateChartData'; type KeywordProps = { keywordData: KeywordType, @@ -82,16 +83,6 @@ const Keyword = (props: KeywordProps) => { const optionsButtonStyle = 'block px-2 py-2 cursor-pointer hover:bg-indigo-50 hover:text-blue-700'; - const renderPosition = (pos:number, type?:string) => { - if (!updating && pos === 0) { - return {'>100'}; - } - if (updating && type !== 'sc') { - return ; - } - return pos; - }; - return (
{
- {renderPosition(position)} + {!updating && positionChange > 0 && ▲ {positionChange}} {!updating && positionChange < 0 && ▼ {positionChange}}
@@ -164,7 +155,10 @@ const Keyword = (props: KeywordProps) => { relative flex justify-between text-center lg:flex-1 lg:text-sm lg:m-0 lg:mt-0 lg:border-t-0 lg:pt-0 lg:top-0'> SC Position: - {renderPosition(keywordData?.scData?.position[scDataType as keyof KeywordSCDataChild] || 0, 'sc')} + Impressions: {keywordData?.scData?.impressions[scDataType as keyof KeywordSCDataChild] || 0} diff --git a/components/keywords/KeywordDetails.tsx b/components/keywords/KeywordDetails.tsx index bcdc8c8..dbec5c7 100644 --- a/components/keywords/KeywordDetails.tsx +++ b/components/keywords/KeywordDetails.tsx @@ -1,10 +1,12 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useLayoutEffect, useMemo, useRef, useState } from 'react'; import dayjs from 'dayjs'; import Icon from '../common/Icon'; import countries from '../../utils/countries'; import Chart from '../common/Chart'; import SelectField from '../common/SelectField'; -import { generateTheChartData } from '../common/generateChartData'; +import { useFetchSingleKeyword } from '../../services/keywords'; +import useOnKey from '../../hooks/useOnKey'; +import { generateTheChartData } from '../../utils/client/generateChartData'; type KeywordDetailsProps = { keyword: KeywordType, @@ -13,11 +15,12 @@ type KeywordDetailsProps = { const KeywordDetails = ({ keyword, closeDetails }:KeywordDetailsProps) => { const updatedDate = new Date(keyword.lastUpdated); - const [keywordHistory, setKeywordHistory] = useState(keyword.history); - const [keywordSearchResult, setKeywordSearchResult] = useState([]); const [chartTime, setChartTime] = useState('30'); const searchResultContainer = useRef(null); const searchResultFound = useRef(null); + const { data: keywordData } = useFetchSingleKeyword(keyword.ID); + const keywordHistory: KeywordHistory = keywordData?.history || keyword.history; + const keywordSearchResult: KeywordLastResult = keywordData?.searchResult || keyword.history; const dateOptions = [ { label: 'Last 7 Days', value: '7' }, { label: 'Last 30 Days', value: '30' }, @@ -26,39 +29,9 @@ const KeywordDetails = ({ keyword, closeDetails }:KeywordDetailsProps) => { { label: 'All Time', value: 'all' }, ]; - useEffect(() => { - const fetchFullKeyword = async () => { - try { - const fetchURL = `${window.location.origin}/api/keyword?id=${keyword.ID}`; - const res = await fetch(fetchURL, { method: 'GET' }).then((result) => result.json()); - if (res.keyword) { - console.log(res.keyword, new Date().getTime()); - setKeywordHistory(res.keyword.history || []); - setKeywordSearchResult(res.keyword.lastResult || []); - } - } catch (error) { - console.log(error); - } - }; - if (keyword.lastResult.length === 0) { - fetchFullKeyword(); - } - }, [keyword]); + useOnKey('Escape', closeDetails); - useEffect(() => { - const closeModalonEsc = (event:KeyboardEvent) => { - if (event.key === 'Escape') { - console.log(event.key); - closeDetails(); - } - }; - window.addEventListener('keydown', closeModalonEsc, false); - return () => { - window.removeEventListener('keydown', closeModalonEsc, false); - }; - }, [closeDetails]); - - useEffect(() => { + useLayoutEffect(() => { if (keyword.position < 100 && keyword.position > 0 && searchResultFound?.current) { searchResultFound.current.scrollIntoView({ behavior: 'smooth', diff --git a/components/keywords/KeywordFilter.tsx b/components/keywords/KeywordFilter.tsx index a3e4992..46aaa7e 100644 --- a/components/keywords/KeywordFilter.tsx +++ b/components/keywords/KeywordFilter.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useMemo } from 'react'; import Icon from '../common/Icon'; import SelectField, { SelectionOption } from '../common/SelectField'; import countries from '../../utils/countries'; @@ -17,11 +17,6 @@ type KeywordFilterProps = { SCcountries?: string[]; } -type KeywordCountState = { - desktop: number, - mobile: number -} - const KeywordFilters = (props: KeywordFilterProps) => { const { device, @@ -36,20 +31,14 @@ const KeywordFilters = (props: KeywordFilterProps) => { integratedConsole = false, SCcountries = [], } = props; - const [keywordCounts, setKeywordCounts] = useState({ desktop: 0, mobile: 0 }); const [sortOptions, showSortOptions] = useState(false); const [filterOptions, showFilterOptions] = useState(false); - useEffect(() => { - const keyWordCount = { desktop: 0, mobile: 0 }; - keywords.forEach((k) => { - if (k.device === 'desktop') { - keyWordCount.desktop += 1; - } else { - keyWordCount.mobile += 1; - } - }); - setKeywordCounts(keyWordCount); + const keywordCounts = useMemo(() => { + return keywords.reduce((acc, k) => ({ + desktop: k.device === 'desktop' ? acc.desktop + 1 : acc.desktop, + mobile: k.device !== 'desktop' ? acc.mobile + 1 : acc.mobile, + }), { desktop: 0, mobile: 0 }); }, [keywords]); const filterCountry = (cntrs:string[]) => filterKeywords({ ...filterParams, countries: cntrs }); diff --git a/components/keywords/KeywordPosition.tsx b/components/keywords/KeywordPosition.tsx new file mode 100644 index 0000000..24a2265 --- /dev/null +++ b/components/keywords/KeywordPosition.tsx @@ -0,0 +1,19 @@ +import Icon from '../common/Icon'; + +type KeywordPositionProps = { + position: number, + updating?: boolean, + type?: string, +} + +const KeywordPosition = ({ position = 0, type = '', updating = false }:KeywordPositionProps) => { + if (!updating && position === 0) { + return {'>100'}; + } + if (updating && type !== 'sc') { + return ; + } + return <>{Math.round(position)}; +}; + +export default KeywordPosition; diff --git a/components/keywords/KeywordsTable.tsx b/components/keywords/KeywordsTable.tsx index cc8f856..e31daad 100644 --- a/components/keywords/KeywordsTable.tsx +++ b/components/keywords/KeywordsTable.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo, useEffect } from 'react'; +import React, { useState, useMemo } from 'react'; import { Toaster } from 'react-hot-toast'; import { CSSTransition } from 'react-transition-group'; import { FixedSizeList as List, ListChildComponentProps } from 'react-window'; @@ -12,6 +12,8 @@ import Modal from '../common/Modal'; import { useDeleteKeywords, useFavKeywords, useRefreshKeywords } from '../../services/keywords'; import KeywordTagManager from './KeywordTagManager'; import AddTags from './AddTags'; +import useWindowResize from '../../hooks/useWindowResize'; +import useIsMobile from '../../hooks/useIsMobile'; type KeywordsTableProps = { domain: DomainType | null, @@ -31,7 +33,6 @@ const KeywordsTable = (props: KeywordsTableProps) => { const [showRemoveModal, setShowRemoveModal] = useState(false); const [showTagManager, setShowTagManager] = useState(null); const [showAddTags, setShowAddTags] = useState(false); - const [isMobile, setIsMobile] = useState(false); const [SCListHeight, setSCListHeight] = useState(500); const [filterParams, setFilterParams] = useState({ countries: [], tags: [], search: '' }); const [sortBy, setSortBy] = useState('date_asc'); @@ -40,6 +41,8 @@ const KeywordsTable = (props: KeywordsTableProps) => { const { mutate: deleteMutate } = useDeleteKeywords(() => {}); const { mutate: favoriteMutate } = useFavKeywords(() => {}); const { mutate: refreshMutate } = useRefreshKeywords(() => {}); + const [isMobile] = useIsMobile(); + useWindowResize(() => setSCListHeight(window.innerHeight - (isMobile ? 200 : 400))); const scDataObject:{ [k:string] : string} = { threeDays: 'Last Three Days', @@ -50,16 +53,6 @@ const KeywordsTable = (props: KeywordsTableProps) => { avgThirtyDays: 'Last Thirty Days Avg', }; - useEffect(() => { - setIsMobile(!!(window.matchMedia('only screen and (max-width: 760px)').matches)); - const resizeList = () => setSCListHeight(window.innerHeight - (isMobile ? 200 : 400)); - resizeList(); - window.addEventListener('resize', resizeList); - return () => { - window.removeEventListener('resize', resizeList); - }; - }, [isMobile]); - const processedKeywords: {[key:string] : KeywordType[]} = useMemo(() => { const procKeywords = keywords.filter((x) => x.device === device); const filteredKeywords = filterKeywords(procKeywords, filterParams); diff --git a/components/keywords/SCKeyword.tsx b/components/keywords/SCKeyword.tsx index 15c879f..3318b97 100644 --- a/components/keywords/SCKeyword.tsx +++ b/components/keywords/SCKeyword.tsx @@ -1,6 +1,8 @@ import React from 'react'; import Icon from '../common/Icon'; import countries from '../../utils/countries'; +import KeywordPosition from './KeywordPosition'; +import { formattedNum } from '../../utils/client/helpers'; type SCKeywordProps = { keywordData: SearchAnalyticsItem, @@ -15,13 +17,6 @@ const SCKeyword = (props: SCKeywordProps) => { const { keywordData, selected, lastItem, selectKeyword, style, isTracked = false } = props; const { keyword, uid, position, country, impressions, ctr, clicks } = keywordData; - const renderPosition = () => { - if (position === 0) { - return {'>100'}; - } - return Math.round(position); - }; - return (
{
- {renderPosition()} + Position
@@ -53,14 +48,14 @@ const SCKeyword = (props: SCKeywordProps) => { - {new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(impressions)} + {formattedNum(impressions)}
- {new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(clicks)} + {formattedNum(clicks)}
diff --git a/components/keywords/SCKeywordsTable.tsx b/components/keywords/SCKeywordsTable.tsx index 15cf272..89c3c28 100644 --- a/components/keywords/SCKeywordsTable.tsx +++ b/components/keywords/SCKeywordsTable.tsx @@ -1,5 +1,5 @@ import { useRouter } from 'next/router'; -import React, { useState, useMemo, useEffect } from 'react'; +import React, { useState, useMemo } from 'react'; import { Toaster } from 'react-hot-toast'; import { FixedSizeList as List, ListChildComponentProps } from 'react-window'; import { useAddKeywords, useFetchKeywords } from '../../services/keywords'; @@ -7,6 +7,9 @@ import { SCfilterKeywords, SCkeywordsByDevice, SCsortKeywords } from '../../util import Icon from '../common/Icon'; import KeywordFilters from './KeywordFilter'; import SCKeyword from './SCKeyword'; +import useWindowResize from '../../hooks/useWindowResize'; +import useIsMobile from '../../hooks/useIsMobile'; +import { formattedNum } from '../../utils/client/helpers'; type SCKeywordsTableProps = { domain: DomainType | null, @@ -27,11 +30,13 @@ const SCKeywordsTable = ({ domain, keywords = [], isLoading = true, isConsoleInt const [selectedKeywords, setSelectedKeywords] = useState([]); const [filterParams, setFilterParams] = useState({ countries: [], tags: [], search: '' }); const [sortBy, setSortBy] = useState('imp_desc'); - const [isMobile, setIsMobile] = useState(false); const [SCListHeight, setSCListHeight] = useState(500); const { keywordsData } = useFetchKeywords(router); const addedkeywords: string[] = keywordsData?.keywords?.map((key: KeywordType) => `${key.keyword}:${key.country}:${key.device}`) || []; const { mutate: addKeywords } = useAddKeywords(() => { if (domain && domain.slug) router.push(`/domain/${domain.slug}`); }); + const [isMobile] = useIsMobile(); + useWindowResize(() => setSCListHeight(window.innerHeight - (isMobile ? 200 : 400))); + const finalKeywords: {[key:string] : SCKeywordType[] } = useMemo(() => { const procKeywords = keywords.filter((x) => x.device === device); const filteredKeywords = SCfilterKeywords(procKeywords, filterParams); @@ -71,16 +76,6 @@ const SCKeywordsTable = ({ domain, keywords = [], isLoading = true, isConsoleInt }; }, [finalKeywords, device]); - useEffect(() => { - setIsMobile(!!(window.matchMedia('only screen and (max-width: 760px)').matches)); - const resizeList = () => setSCListHeight(window.innerHeight - (isMobile ? 200 : 400)); - resizeList(); - window.addEventListener('resize', resizeList); - return () => { - window.removeEventListener('resize', resizeList); - }; - }, [isMobile]); - const selectKeyword = (keywordID: string) => { console.log('Select Keyword: ', keywordID); let updatedSelectd = [...selectedKeywords, keywordID]; @@ -194,10 +189,10 @@ const SCKeywordsTable = ({ domain, keywords = [], isLoading = true, isConsoleInt {viewSummary.position} - {new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(viewSummary.impressions)} + {formattedNum(viewSummary.impressions)} - {new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(viewSummary.visits)} + {formattedNum(viewSummary.visits)} {new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(viewSummary.ctr)}% diff --git a/components/settings/Settings.tsx b/components/settings/Settings.tsx index a15b5e1..7476705 100644 --- a/components/settings/Settings.tsx +++ b/components/settings/Settings.tsx @@ -4,6 +4,7 @@ import { useFetchSettings, useUpdateSettings } from '../../services/settings'; import Icon from '../common/Icon'; import NotificationSettings from './NotificationSettings'; import ScraperSettings from './ScraperSettings'; +import useOnKey from '../../hooks/useOnKey'; type SettingsProps = { closeSettings: Function, @@ -34,6 +35,7 @@ const Settings = ({ closeSettings }:SettingsProps) => { const [settingsError, setSettingsError] = useState(null); const { mutate: updateMutate, isLoading: isUpdating } = useUpdateSettings(() => console.log('')); const { data: appSettings, isLoading } = useFetchSettings(); + useOnKey('Escape', closeSettings); useEffect(() => { if (appSettings && appSettings.settings) { @@ -41,19 +43,6 @@ const Settings = ({ closeSettings }:SettingsProps) => { } }, [appSettings]); - useEffect(() => { - const closeModalonEsc = (event:KeyboardEvent) => { - if (event.key === 'Escape') { - console.log(event.key); - closeSettings(); - } - }; - window.addEventListener('keydown', closeModalonEsc, false); - return () => { - window.removeEventListener('keydown', closeModalonEsc, false); - }; - }, [closeSettings]); - const closeOnBGClick = (e:React.SyntheticEvent) => { e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); diff --git a/hooks/useIsMobile.tsx b/hooks/useIsMobile.tsx new file mode 100644 index 0000000..416b936 --- /dev/null +++ b/hooks/useIsMobile.tsx @@ -0,0 +1,12 @@ +import { useEffect, useState } from 'react'; + +const useIsMobile = () => { + const [isMobile, setIsMobile] = useState(false); + useEffect(() => { + setIsMobile(!!(window.matchMedia('only screen and (max-width: 760px)').matches)); + }, []); + + return [isMobile]; +}; + +export default useIsMobile; diff --git a/hooks/useOnKey.tsx b/hooks/useOnKey.tsx new file mode 100644 index 0000000..016e12c --- /dev/null +++ b/hooks/useOnKey.tsx @@ -0,0 +1,17 @@ +import { useEffect } from 'react'; + +const useOnKey = (key:string, onPress: Function) => { + useEffect(() => { + const closeModalonEsc = (event:KeyboardEvent) => { + if (event.key === key) { + onPress(); + } + }; + window.addEventListener('keydown', closeModalonEsc, false); + return () => { + window.removeEventListener('keydown', closeModalonEsc, false); + }; + }, [key, onPress]); +}; + +export default useOnKey; diff --git a/hooks/useWindowResize.tsx b/hooks/useWindowResize.tsx new file mode 100644 index 0000000..d5d1ef5 --- /dev/null +++ b/hooks/useWindowResize.tsx @@ -0,0 +1,13 @@ +import { useEffect } from 'react'; + +const useWindowResize = (onResize: () => void) => { + useEffect(() => { + onResize(); + window.addEventListener('resize', onResize); + return () => { + window.removeEventListener('resize', onResize); + }; + }, [onResize]); +}; + +export default useWindowResize; diff --git a/services/keywords.tsx b/services/keywords.tsx index 12b3568..d47ba4c 100644 --- a/services/keywords.tsx +++ b/services/keywords.tsx @@ -153,3 +153,23 @@ export function useRefreshKeywords(onSuccess:Function) { }, }); } + +export function useFetchSingleKeyword(keywordID:number) { + return useQuery(['keyword', keywordID], async () => { + try { + const fetchURL = `${window.location.origin}/api/keyword?id=${keywordID}`; + const res = await fetch(fetchURL, { method: 'GET' }).then((result) => result.json()); + if (res.status >= 400 && res.status < 600) { + throw new Error('Bad response from server'); + } + return { history: res.keyword.history || [], searchResult: res.keyword.lastResult || [] }; + } catch (error) { + throw new Error('Error Loading Keyword Details'); + } + }, { + onError: () => { + console.log('Error Loading Keyword Data!!!'); + toast('Error Loading Keyword Details.', { icon: '⚠️' }); + }, + }); +} diff --git a/components/common/generateChartData.ts b/utils/client/generateChartData.ts similarity index 100% rename from components/common/generateChartData.ts rename to utils/client/generateChartData.ts diff --git a/utils/client/helpers.ts b/utils/client/helpers.ts new file mode 100644 index 0000000..7738165 --- /dev/null +++ b/utils/client/helpers.ts @@ -0,0 +1,2 @@ +/* eslint-disable import/prefer-default-export */ +export const formattedNum = (num:number) => new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(num);