-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Perf: Split SearchRouter to State and Actions
#79753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fc9d7bf
5352122
fbc3d90
728c413
242e7d0
092408d
b90935c
ef66bc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import React, {useContext, useEffect, useMemo, useRef, useState} from 'react'; | ||
| import React, {useContext, useEffect, useRef, useState} from 'react'; | ||
| import type {AnimatedTextInputRef} from '@components/RNTextInput'; | ||
| import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; | ||
| import {navigationRef} from '@libs/Navigation/Navigation'; | ||
|
|
@@ -9,8 +9,11 @@ import NAVIGATORS from '@src/NAVIGATORS'; | |
| import SCREENS from '@src/SCREENS'; | ||
| import type ChildrenProps from '@src/types/utils/ChildrenProps'; | ||
|
|
||
| type SearchRouterContext = { | ||
| type SearchRouterStateContextType = { | ||
| isSearchRouterDisplayed: boolean; | ||
| }; | ||
|
|
||
| type SearchRouterActionsContextType = { | ||
| openSearchRouter: () => void; | ||
| closeSearchRouter: () => void; | ||
| toggleSearch: () => void; | ||
|
|
@@ -22,16 +25,17 @@ type HistoryState = { | |
| isSearchModalOpen?: boolean; | ||
| }; | ||
|
|
||
| const defaultSearchContext: SearchRouterContext = { | ||
| isSearchRouterDisplayed: false, | ||
| const defaultSearchRouterActionsContext: SearchRouterActionsContextType = { | ||
| openSearchRouter: () => {}, | ||
| closeSearchRouter: () => {}, | ||
| toggleSearch: () => {}, | ||
| registerSearchPageInput: () => {}, | ||
| unregisterSearchPageInput: () => {}, | ||
| }; | ||
|
|
||
| const Context = React.createContext<SearchRouterContext>(defaultSearchContext); | ||
| const SearchRouterStateContext = React.createContext<SearchRouterStateContextType>({isSearchRouterDisplayed: false}); | ||
|
|
||
| const SearchRouterActionsContext = React.createContext<SearchRouterActionsContextType>(defaultSearchRouterActionsContext); | ||
|
|
||
| const isBrowserWithHistory = typeof window !== 'undefined' && typeof window.history !== 'undefined'; | ||
| const canListenPopState = typeof window !== 'undefined' && typeof window.addEventListener === 'function'; | ||
|
|
@@ -69,87 +73,98 @@ function SearchRouterContextProvider({children}: ChildrenProps) { | |
| return () => window.removeEventListener('popstate', handlePopState); | ||
| }, []); | ||
|
|
||
| const routerContext = useMemo(() => { | ||
| const openSearchRouter = () => { | ||
| if (isBrowserWithHistory) { | ||
| window.history.pushState({isSearchModalOpen: true} satisfies HistoryState, ''); | ||
| } | ||
| close( | ||
| () => { | ||
| setIsSearchRouterDisplayed(true); | ||
| searchRouterDisplayedRef.current = true; | ||
| }, | ||
| false, | ||
| true, | ||
| ); | ||
| }; | ||
| const closeSearchRouter = () => { | ||
| setIsSearchRouterDisplayed(false); | ||
| searchRouterDisplayedRef.current = false; | ||
| if (isBrowserWithHistory) { | ||
| const state = window.history.state as HistoryState | null; | ||
| if (state?.isSearchModalOpen) { | ||
| window.history.replaceState({isSearchModalOpen: false} satisfies HistoryState, ''); | ||
| } | ||
| const openSearchRouter = () => { | ||
| if (isBrowserWithHistory) { | ||
| window.history.pushState({isSearchModalOpen: true} satisfies HistoryState, ''); | ||
| } | ||
| close( | ||
| () => { | ||
| setIsSearchRouterDisplayed(true); | ||
| searchRouterDisplayedRef.current = true; | ||
| }, | ||
| false, | ||
| true, | ||
| ); | ||
| }; | ||
| const closeSearchRouter = () => { | ||
| setIsSearchRouterDisplayed(false); | ||
| searchRouterDisplayedRef.current = false; | ||
| if (isBrowserWithHistory) { | ||
| const state = window.history.state as HistoryState | null; | ||
| if (state?.isSearchModalOpen) { | ||
| window.history.replaceState({isSearchModalOpen: false} satisfies HistoryState, ''); | ||
| } | ||
| }; | ||
|
|
||
| const startSearchRouterOpenSpan = () => { | ||
| startSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, { | ||
| name: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, | ||
| op: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, | ||
| attributes: { | ||
| trigger: 'keyboard', | ||
| }, | ||
| }); | ||
| }; | ||
|
|
||
| // There are callbacks that live outside of React render-loop and interact with SearchRouter | ||
| // So we need a function that is based on ref to correctly open/close it | ||
| // When user is on `/search` page we focus the Input instead of showing router | ||
| const toggleSearch = () => { | ||
| const searchFullScreenRoutes = navigationRef.getRootState()?.routes.findLast((route) => route.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR); | ||
| const lastRoute = searchFullScreenRoutes?.state?.routes?.at(-1); | ||
| const isUserOnSearchPage = isSearchTopmostFullScreenRoute() && lastRoute?.name === SCREENS.SEARCH.ROOT; | ||
|
|
||
| if (isUserOnSearchPage && searchPageInputRef.current) { | ||
| if (searchPageInputRef.current.isFocused()) { | ||
| searchPageInputRef.current.blur(); | ||
| } else { | ||
| startSearchRouterOpenSpan(); | ||
| searchPageInputRef.current.focus(); | ||
| } | ||
| } else if (searchRouterDisplayedRef.current) { | ||
| closeSearchRouter(); | ||
| } | ||
| }; | ||
|
|
||
| const startSearchRouterOpenSpan = () => { | ||
| startSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, { | ||
| name: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, | ||
| op: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, | ||
| attributes: { | ||
| trigger: 'keyboard', | ||
| }, | ||
| }); | ||
| }; | ||
|
|
||
| // There are callbacks that live outside of React render-loop and interact with SearchRouter | ||
| // So we need a function that is based on ref to correctly open/close it | ||
| // When user is on `/search` page we focus the Input instead of showing router | ||
| const toggleSearch = () => { | ||
| const searchFullScreenRoutes = navigationRef.getRootState()?.routes.findLast((route) => route.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR); | ||
| const lastRoute = searchFullScreenRoutes?.state?.routes?.at(-1); | ||
| const isUserOnSearchPage = isSearchTopmostFullScreenRoute() && lastRoute?.name === SCREENS.SEARCH.ROOT; | ||
|
|
||
| if (isUserOnSearchPage && searchPageInputRef.current) { | ||
| if (searchPageInputRef.current.isFocused()) { | ||
| searchPageInputRef.current.blur(); | ||
| } else { | ||
| startSearchRouterOpenSpan(); | ||
| openSearchRouter(); | ||
| searchPageInputRef.current.focus(); | ||
| } | ||
| }; | ||
|
|
||
| const registerSearchPageInput = (ref: AnimatedTextInputRef) => { | ||
| searchPageInputRef.current = ref; | ||
| }; | ||
|
|
||
| const unregisterSearchPageInput = () => { | ||
| searchPageInputRef.current = undefined; | ||
| }; | ||
|
|
||
| return { | ||
| isSearchRouterDisplayed, | ||
| openSearchRouter, | ||
| closeSearchRouter, | ||
| toggleSearch, | ||
| registerSearchPageInput, | ||
| unregisterSearchPageInput, | ||
| }; | ||
| }, [isSearchRouterDisplayed, setIsSearchRouterDisplayed]); | ||
| } else if (searchRouterDisplayedRef.current) { | ||
| closeSearchRouter(); | ||
| } else { | ||
| startSearchRouterOpenSpan(); | ||
| openSearchRouter(); | ||
| } | ||
| }; | ||
|
|
||
| const registerSearchPageInput = (ref: AnimatedTextInputRef) => { | ||
| searchPageInputRef.current = ref; | ||
| }; | ||
|
|
||
| const unregisterSearchPageInput = () => { | ||
| searchPageInputRef.current = undefined; | ||
| }; | ||
|
|
||
| // Because of the React Compiler we don't need to memoize it manually | ||
| // eslint-disable-next-line react/jsx-no-constructed-context-values | ||
|
Comment on lines
+141
to
+142
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it cause lint error without this comment?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it does |
||
| const actionsContextValue = { | ||
| openSearchRouter, | ||
| closeSearchRouter, | ||
| toggleSearch, | ||
| registerSearchPageInput, | ||
| unregisterSearchPageInput, | ||
| }; | ||
mountiny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Because of the React Compiler we don't need to memoize it manually | ||
| // eslint-disable-next-line react/jsx-no-constructed-context-values | ||
|
Comment on lines
+151
to
+152
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, there is a lint error |
||
| const stateContextValue = {isSearchRouterDisplayed}; | ||
|
|
||
| return ( | ||
| <SearchRouterActionsContext.Provider value={actionsContextValue}> | ||
| <SearchRouterStateContext.Provider value={stateContextValue}>{children}</SearchRouterStateContext.Provider> | ||
| </SearchRouterActionsContext.Provider> | ||
| ); | ||
| } | ||
|
|
||
| return <Context.Provider value={routerContext}>{children}</Context.Provider>; | ||
| function useSearchRouterState() { | ||
| return useContext(SearchRouterStateContext); | ||
| } | ||
|
|
||
| function useSearchRouterContext() { | ||
| return useContext(Context); | ||
| function useSearchRouterActions() { | ||
| return useContext(SearchRouterActionsContext); | ||
| } | ||
|
|
||
| export {SearchRouterContextProvider, useSearchRouterContext}; | ||
| export {SearchRouterContextProvider, useSearchRouterState, useSearchRouterActions}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Run |
Uh oh!
There was an error while loading. Please reload this page.