Changeset View
Changeset View
Standalone View
Standalone View
lib/hooks/search-sidebars.js
// @flow | // @flow | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { sidebarInfoSelector } from '../selectors/thread-selectors'; | import { sidebarInfoSelector } from '../selectors/thread-selectors'; | ||||
import SearchIndex from '../shared/search-index'; | import SearchIndex from '../shared/search-index'; | ||||
import { threadSearchText } from '../shared/thread-utils'; | import { threadSearchText } from '../shared/thread-utils'; | ||||
import type { SetState } from '../types/hook-types'; | import type { SetState } from '../types/hook-types'; | ||||
import type { SidebarInfo, ThreadInfo } from '../types/thread-types'; | import type { SidebarInfo, ThreadInfo } from '../types/thread-types'; | ||||
import { useSelector } from '../utils/redux-utils'; | import { useSelector } from '../utils/redux-utils'; | ||||
type SidebarSearchState = { | |||||
+text: string, | |||||
+results: $ReadOnlySet<string>, | |||||
}; | |||||
function useSearchSidebars( | function useSearchSidebars( | ||||
threadInfo: ThreadInfo, | threadInfo: ThreadInfo, | ||||
text: string, | |||||
setTextSearchText: SetState<string>, | |||||
): { | ): { | ||||
+listData: $ReadOnlyArray<SidebarInfo>, | +listData: $ReadOnlyArray<SidebarInfo>, | ||||
+searchState: SidebarSearchState, | +setSearchState: SetState<$ReadOnlySet<string>>, | ||||
+setSearchState: SetState<SidebarSearchState>, | |||||
+onChangeSearchText: ( | +onChangeSearchText: ( | ||||
text: SyntheticEvent<HTMLInputElement> | string, | text: SyntheticEvent<HTMLInputElement> | string, | ||||
) => mixed, | ) => mixed, | ||||
+clearQuery: (event: SyntheticEvent<HTMLAnchorElement>) => void, | +clearQuery: (event: SyntheticEvent<HTMLAnchorElement>) => void, | ||||
} { | } { | ||||
const [searchState, setSearchState] = React.useState({ | const [searchState, setSearchState] = React.useState(new Set<string>()); | ||||
text: '', | |||||
results: new Set<string>(), | |||||
}); | |||||
const userInfos = useSelector(state => state.userStore.userInfos); | const userInfos = useSelector(state => state.userStore.userInfos); | ||||
const sidebarInfos = useSelector( | const sidebarInfos = useSelector( | ||||
state => sidebarInfoSelector(state)[threadInfo.id] ?? [], | state => sidebarInfoSelector(state)[threadInfo.id] ?? [], | ||||
); | ); | ||||
const listData = React.useMemo(() => { | const listData = React.useMemo(() => { | ||||
if (!searchState.text) { | if (!text) { | ||||
return sidebarInfos; | return sidebarInfos; | ||||
} | } | ||||
return sidebarInfos.filter(sidebarInfo => | return sidebarInfos.filter(sidebarInfo => | ||||
searchState.results.has(sidebarInfo.threadInfo.id), | searchState.has(sidebarInfo.threadInfo.id), | ||||
); | ); | ||||
}, [sidebarInfos, searchState]); | }, [sidebarInfos, searchState, text]); | ||||
const viewerID = useSelector( | const viewerID = useSelector( | ||||
state => state.currentUserInfo && state.currentUserInfo.id, | state => state.currentUserInfo && state.currentUserInfo.id, | ||||
); | ); | ||||
const searchIndex = React.useMemo(() => { | const searchIndex = React.useMemo(() => { | ||||
const index = new SearchIndex(); | const index = new SearchIndex(); | ||||
for (const sidebarInfo of sidebarInfos) { | for (const sidebarInfo of sidebarInfos) { | ||||
const threadInfoFromSidebarInfo = sidebarInfo.threadInfo; | const threadInfoFromSidebarInfo = sidebarInfo.threadInfo; | ||||
index.addEntry( | index.addEntry( | ||||
threadInfoFromSidebarInfo.id, | threadInfoFromSidebarInfo.id, | ||||
threadSearchText(threadInfoFromSidebarInfo, userInfos, viewerID), | threadSearchText(threadInfoFromSidebarInfo, userInfos, viewerID), | ||||
); | ); | ||||
} | } | ||||
return index; | return index; | ||||
}, [sidebarInfos, userInfos, viewerID]); | }, [sidebarInfos, userInfos, viewerID]); | ||||
const onChangeSearchText = React.useCallback( | const onChangeSearchText = React.useCallback( | ||||
(event: SyntheticEvent<HTMLInputElement> | string) => { | (event: SyntheticEvent<HTMLInputElement> | string) => { | ||||
let text; | let onChangeText; | ||||
if (typeof event === 'string') { | if (typeof event === 'string') { | ||||
text = event; | onChangeText = event; | ||||
} else { | } else { | ||||
text = event.currentTarget.value; | onChangeText = event.currentTarget.value; | ||||
} | } | ||||
setSearchState({ | setSearchState(new Set(searchIndex.getSearchResults(onChangeText))); | ||||
text, | setTextSearchText(onChangeText); | ||||
results: new Set(searchIndex.getSearchResults(text)), | |||||
}); | |||||
}, | }, | ||||
[searchIndex, setSearchState], | [searchIndex, setSearchState, setTextSearchText], | ||||
); | ); | ||||
const clearQuery = React.useCallback( | const clearQuery = React.useCallback( | ||||
(event: SyntheticEvent<HTMLAnchorElement>) => { | (event: SyntheticEvent<HTMLAnchorElement>) => { | ||||
event.preventDefault(); | event.preventDefault(); | ||||
setSearchState({ text: '', results: new Set() }); | setSearchState(new Set()); | ||||
setTextSearchText(''); | |||||
}, | }, | ||||
[setSearchState], | [setSearchState, setTextSearchText], | ||||
); | ); | ||||
React.useEffect(() => { | React.useEffect(() => { | ||||
setSearchState(curState => ({ | setSearchState(new Set(searchIndex.getSearchResults(text))); | ||||
...curState, | }, [searchIndex, setSearchState, text]); | ||||
results: new Set(searchIndex.getSearchResults(curState.text)), | |||||
})); | |||||
}, [searchIndex, setSearchState]); | |||||
return React.useMemo( | return React.useMemo( | ||||
() => ({ | () => ({ | ||||
listData, | listData, | ||||
searchState, | |||||
setSearchState, | setSearchState, | ||||
onChangeSearchText, | onChangeSearchText, | ||||
clearQuery, | clearQuery, | ||||
}), | }), | ||||
[listData, setSearchState, searchState, onChangeSearchText, clearQuery], | [listData, setSearchState, onChangeSearchText, clearQuery], | ||||
); | ); | ||||
} | } | ||||
export { useSearchSidebars }; | export { useSearchSidebars }; |