From 908a439c4181e8ff6608ca644c902d8e2bde0dab Mon Sep 17 00:00:00 2001 From: alinkedd Date: Mon, 2 Sep 2024 11:03:24 +0200 Subject: [PATCH] Search PR review refactoring proposal --- components/search/context.ts | 8 --- components/search/{dummy.tsx => fallback.tsx} | 8 +-- components/search/index.tsx | 45 ++++++++---- components/search/useFocus.tsx | 68 +++++++++++++++++++ components/search/useSearch.ts | 49 ------------- .../search/{hit.tsx => widgets/hit/index.tsx} | 2 +- .../{validation.ts => widgets/hit/models.ts} | 0 .../search/{ => widgets/hit}/sectionIcon.tsx | 0 components/search/{ => widgets/hit}/useHit.ts | 10 ++- .../{algoliaSearch.tsx => widgets/index.tsx} | 10 ++- components/search/{ => widgets}/noResults.tsx | 0 .../{ => widgets}/noResultsBoundary.tsx | 0 components/search/withInstantSearch.tsx | 38 ----------- 13 files changed, 119 insertions(+), 119 deletions(-) delete mode 100644 components/search/context.ts rename components/search/{dummy.tsx => fallback.tsx} (92%) create mode 100644 components/search/useFocus.tsx delete mode 100644 components/search/useSearch.ts rename components/search/{hit.tsx => widgets/hit/index.tsx} (95%) rename components/search/{validation.ts => widgets/hit/models.ts} (100%) rename components/search/{ => widgets/hit}/sectionIcon.tsx (100%) rename components/search/{ => widgets/hit}/useHit.ts (80%) rename components/search/{algoliaSearch.tsx => widgets/index.tsx} (95%) rename components/search/{ => widgets}/noResults.tsx (100%) rename components/search/{ => widgets}/noResultsBoundary.tsx (100%) delete mode 100644 components/search/withInstantSearch.tsx diff --git a/components/search/context.ts b/components/search/context.ts deleted file mode 100644 index 75a03e4..0000000 --- a/components/search/context.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type Dispatch, type SetStateAction, createContext } from 'react'; - -const SearchFocusedContext = createContext<{ - isFocused: boolean; - setIsFocused: Dispatch>; -}>({ isFocused: false, setIsFocused: () => {} }); - -export default SearchFocusedContext; diff --git a/components/search/dummy.tsx b/components/search/fallback.tsx similarity index 92% rename from components/search/dummy.tsx rename to components/search/fallback.tsx index 6e05f20..28bc21d 100644 --- a/components/search/dummy.tsx +++ b/components/search/fallback.tsx @@ -1,9 +1,9 @@ -import { useCallback, useContext } from 'react'; +import { useCallback } from 'react'; -import SearchFocusedContext from './context'; +import useFocus from './useFocus'; -export default function SearchDummy() { - const { isFocused, setIsFocused } = useContext(SearchFocusedContext); +export default function SearchFallback() { + const { isFocused, setIsFocused } = useFocus(); const handleFocus = useCallback(() => setIsFocused(true), [setIsFocused]); return (
diff --git a/components/search/index.tsx b/components/search/index.tsx index 79d9ff7..aa31d77 100644 --- a/components/search/index.tsx +++ b/components/search/index.tsx @@ -1,18 +1,35 @@ -import SearchDummy from './dummy'; -import useSearch from './useSearch'; -import withInstantSearch from './withInstantSearch'; +import { lazy, Suspense, useMemo } from 'react'; +import { InstantSearch } from 'react-instantsearch'; + +import { getAlgoliaIndexName, getAlgoliaClient } from '../../utils/algolia'; + +import { FocusOuterWrapper, FocusProvider } from './useFocus'; + +import SearchFallback from './fallback'; + +const SearchWidgets = lazy(() => import('./widgets')); + +const FUTURE = { + preserveSharedStateOnUnmount: true, +}; + +export default function Search() { + const searchClient = useMemo(() => getAlgoliaClient(), []); + const indexName = useMemo(() => getAlgoliaIndexName(), []); -function Search() { - const { AlgoliaSearch, handleFocus, isFocused, rootReference } = useSearch(); return ( -
- {typeof AlgoliaSearch === 'function' ? ( - - ) : ( - - )} -
+ + + + }> + + + + + ); } - -export default withInstantSearch(Search); diff --git a/components/search/useFocus.tsx b/components/search/useFocus.tsx new file mode 100644 index 0000000..127da1b --- /dev/null +++ b/components/search/useFocus.tsx @@ -0,0 +1,68 @@ +import { + type Dispatch, + type SetStateAction, + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; + +const FocusContext = createContext<{ + isFocused: boolean; + setIsFocused: Dispatch>; +}>({ isFocused: false, setIsFocused: () => {} }); + +export function FocusProvider({ children }) { + const [isFocused, setIsFocused] = useState(false); + + const focusValue = useMemo( + () => ({ isFocused, setIsFocused }), + [isFocused, setIsFocused] + ); + + return ( + {children} + ); +} + +export function FocusOuterWrapper({ children, ...props }) { + const rootReference = useRef(null); + const { isFocused, setIsFocused } = useFocus(); + + const handleOutsideClick = useCallback( + (event) => { + if ( + !rootReference.current || + !rootReference.current.contains(event.target) + ) { + setIsFocused(false); + } + }, + [setIsFocused] + ); + + useEffect(() => { + if (isFocused) { + document.addEventListener('click', handleOutsideClick); + } + return () => { + document.removeEventListener('click', handleOutsideClick); + }; + }, [handleOutsideClick, isFocused]); + + const handleFocus = useCallback(() => setIsFocused(true), [setIsFocused]); + + return ( +
+ {children} +
+ ); +} + +export default function useFocus() { + const { isFocused, setIsFocused } = useContext(FocusContext); + return { isFocused, setIsFocused }; +} diff --git a/components/search/useSearch.ts b/components/search/useSearch.ts deleted file mode 100644 index 7e9ddf6..0000000 --- a/components/search/useSearch.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - type FC, - useCallback, - useContext, - useEffect, - useRef, - useState, -} from 'react'; - -import SearchFocusedContext from './context'; - -export default function useSearch() { - const rootReference = useRef(null); - const [AlgoliaSearch, setAlgoliaSearch] = useState | null>(null); - const { isFocused, setIsFocused } = useContext(SearchFocusedContext); - useEffect(() => { - if (AlgoliaSearch || !isFocused) { - return; - } - import('./algoliaSearch') - .then((mod) => { - setAlgoliaSearch(() => mod.default); - }) - .catch(console.error); - }, [AlgoliaSearch, isFocused]); - const handleOutsideClick = useCallback( - (event) => { - if ( - !rootReference.current || - !rootReference.current.contains(event.target) - ) { - setIsFocused(false); - } - }, - [setIsFocused] - ); - useEffect(() => { - if (isFocused) { - document.addEventListener('click', handleOutsideClick); - } - return () => { - document.removeEventListener('click', handleOutsideClick); - }; - }, [handleOutsideClick, isFocused]); - const handleFocus = useCallback(() => setIsFocused(true), [setIsFocused]); - return { AlgoliaSearch, handleFocus, isFocused, rootReference }; -} diff --git a/components/search/hit.tsx b/components/search/widgets/hit/index.tsx similarity index 95% rename from components/search/hit.tsx rename to components/search/widgets/hit/index.tsx index b801c82..f9b367f 100644 --- a/components/search/hit.tsx +++ b/components/search/widgets/hit/index.tsx @@ -5,7 +5,7 @@ import { Highlight, Snippet } from 'react-instantsearch'; import SectionIcon from './sectionIcon'; import useHit from './useHit'; -import type { Hit } from './validation'; +import type { Hit } from './models'; export default function Hit({ hit }: { hit: Hit & HitType }) { const { handleClick, isLoading } = useHit(); diff --git a/components/search/validation.ts b/components/search/widgets/hit/models.ts similarity index 100% rename from components/search/validation.ts rename to components/search/widgets/hit/models.ts diff --git a/components/search/sectionIcon.tsx b/components/search/widgets/hit/sectionIcon.tsx similarity index 100% rename from components/search/sectionIcon.tsx rename to components/search/widgets/hit/sectionIcon.tsx diff --git a/components/search/useHit.ts b/components/search/widgets/hit/useHit.ts similarity index 80% rename from components/search/useHit.ts rename to components/search/widgets/hit/useHit.ts index 479c51a..153114c 100644 --- a/components/search/useHit.ts +++ b/components/search/widgets/hit/useHit.ts @@ -1,26 +1,30 @@ import { useRouter } from 'next/router'; -import { useCallback, useContext, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; -import SearchFocusedContext from './context'; +import useFocus from '../../useFocus'; export default function useHit() { const router = useRouter(); - const { setIsFocused } = useContext(SearchFocusedContext); + const { setIsFocused } = useFocus(); const [isLoading, setIsLoading] = useState(false); + const handleLoadingEnded = useCallback(() => { setIsLoading(false); router.events.off('routeChangeComplete', handleLoadingEnded); setIsFocused(false); }, [router.events, setIsFocused]); + const handleClick = useCallback(() => { setIsLoading(true); router.events.on('routeChangeComplete', handleLoadingEnded); }, [router.events, handleLoadingEnded]); + useEffect( () => () => { router.events.off('routeChangeComplete', handleLoadingEnded); }, [router.events, handleLoadingEnded] ); + return { handleClick, isLoading }; } diff --git a/components/search/algoliaSearch.tsx b/components/search/widgets/index.tsx similarity index 95% rename from components/search/algoliaSearch.tsx rename to components/search/widgets/index.tsx index d9e15d6..eedf52f 100644 --- a/components/search/algoliaSearch.tsx +++ b/components/search/widgets/index.tsx @@ -11,10 +11,12 @@ import { useInstantSearch, } from 'react-instantsearch'; +import useFocus from '../useFocus'; + import Hit from './hit'; import NoResults from './noResults'; import NoResultsBoundary from './noResultsBoundary'; -import type { Hit as HitType } from './validation'; +import type { Hit as HitType } from './hit/models'; import 'instantsearch.css/themes/satellite.css'; @@ -75,7 +77,9 @@ function transformPageTypeItems(items: MenuItem[]) { })); } -export default function AlgoliaSearch({ isFocused }: { isFocused: boolean }) { +export default function SearchWidgets() { + const { isFocused } = useFocus(); + const hitsClassNames = useMemo['classNames']>( () => ({ item: 'border-ui-sidebar border-b', @@ -83,9 +87,11 @@ export default function AlgoliaSearch({ isFocused }: { isFocused: boolean }) { }), [] ); + const { indexUiState: { menu: { section } = {} }, } = useInstantSearch(); + return ( <> >( - Component: FC -) { - function WithInstantSearch(props: T) { - const searchClient = useMemo(() => getAlgoliaClient(), []); - const indexName = useMemo(() => getAlgoliaIndexName(), []); - const [isFocused, setIsFocused] = useState(false); - const searchFocusedValue = useMemo( - () => ({ isFocused, setIsFocused }), - [isFocused, setIsFocused] - ); - return ( - - - - - - ); - } - WithInstantSearch.displayName = `withInstantSearch(${ - Component.displayName || Component.name || 'Component' - })`; - return WithInstantSearch; -}