diff --git a/web/calendar/thread-picker.react.js b/web/calendar/thread-picker.react.js
index 81633c99e..2a58807f9 100644
--- a/web/calendar/thread-picker.react.js
+++ b/web/calendar/thread-picker.react.js
@@ -1,160 +1,144 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { createSelector } from 'reselect';
import { threadSearchIndex } from 'lib/selectors/nav-selectors';
import { onScreenEntryEditableThreadInfos } from 'lib/selectors/thread-selectors';
-import SearchIndex from 'lib/shared/search-index';
import type { ThreadInfo } from 'lib/types/thread-types';
import { useSelector } from '../redux/redux-utils';
import { htmlTargetFromEvent } from '../vector-utils';
import css from './thread-picker.css';
type OptionProps = {
+threadInfo: ThreadInfo,
+createNewEntry: (threadID: string) => void,
};
function ThreadPickerOption(props: OptionProps) {
const { threadInfo, createNewEntry } = props;
const onClick = React.useCallback(() => createNewEntry(threadInfo.id), [
threadInfo.id,
createNewEntry,
]);
const colorStyle = { backgroundColor: `#${props.threadInfo.color}` };
return (
{props.threadInfo.uiName}
);
}
-type BaseProps = {
+type Props = {
+createNewEntry: (threadID: string) => void,
+closePicker: () => void,
};
-type Props = {
- ...BaseProps,
- +onScreenThreadInfos: $ReadOnlyArray,
- +searchIndex: SearchIndex,
-};
-type State = {
- +searchText: string,
- +searchResults: Set,
-};
-type PropsAndState = { ...Props, ...State };
-
-class ThreadPicker extends React.PureComponent {
- pickerDiv: ?HTMLDivElement;
-
- constructor(props: Props) {
- super(props);
- this.state = {
- searchText: '',
- searchResults: new Set(),
- };
- invariant(
- props.onScreenThreadInfos.length > 0,
- "ThreadPicker can't be open when onScreenThreadInfos is empty",
- );
- }
-
- componentDidMount() {
- invariant(this.pickerDiv, 'pickerDiv ref unset');
- this.pickerDiv.focus();
- }
-
- listDataSelector = createSelector(
- (propsAndState: PropsAndState) => propsAndState.onScreenThreadInfos,
- (propsAndState: PropsAndState) => propsAndState.searchText,
- (propsAndState: PropsAndState) => propsAndState.searchResults,
+
+function ThreadPicker(props: Props): React.Node {
+ const { closePicker, createNewEntry } = props;
+
+ const onScreenThreadInfos = useSelector(onScreenEntryEditableThreadInfos);
+ const searchIndex = useSelector(state => threadSearchIndex(state));
+
+ invariant(
+ onScreenThreadInfos.length > 0,
+ "ThreadPicker can't be open when onScreenThreadInfos is empty",
+ );
+
+ const pickerDivRef = React.useRef(null);
+
+ React.useLayoutEffect(() => {
+ invariant(pickerDivRef, 'pickerDivRef must be set');
+ const { current } = pickerDivRef;
+ current?.focus();
+ }, []);
+
+ const [searchText, setSearchText] = React.useState('');
+ const [searchResults, setSearchResults] = React.useState>(
+ new Set(),
+ );
+
+ const onPickerKeyDown = React.useCallback(
+ (event: SyntheticKeyboardEvent) => {
+ if (event.keyCode === 27) {
+ // esc
+ closePicker();
+ }
+ },
+ [closePicker],
+ );
+
+ const onMouseDown = React.useCallback(
+ (event: SyntheticEvent) => {
+ const target = htmlTargetFromEvent(event);
+ invariant(pickerDivRef, 'pickerDivRef must be set');
+ if (pickerDivRef.current?.contains(target)) {
+ // This prevents onBlur from firing
+ event.preventDefault();
+ }
+ },
+ [],
+ );
+
+ // eslint-disable-next-line no-unused-vars
+ const onChangeSearchText = React.useCallback(
+ (text: string) => {
+ const results = searchIndex.getSearchResults(text);
+ setSearchText(text);
+ setSearchResults(new Set(results));
+ },
+ [searchIndex],
+ );
+
+ const listDataSelector = createSelector(
+ state => state.onScreenThreadInfos,
+ state => state.searchText,
+ state => state.searchResults,
(
threadInfos: $ReadOnlyArray,
text: string,
- searchResults: Set,
+ results: Set,
) =>
text
- ? threadInfos.filter(threadInfo => searchResults.has(threadInfo.id))
+ ? threadInfos.filter(threadInfo => results.has(threadInfo.id))
: [...threadInfos],
);
- get getListData() {
- return this.listDataSelector({ ...this.props, ...this.state });
- }
-
- render() {
- const length = this.props.onScreenThreadInfos.length;
- invariant(
- length > 0,
- "ThreadPicker can't be open when onScreenThreadInfos is empty",
- );
+ const threads = useSelector(() =>
+ listDataSelector({
+ onScreenThreadInfos,
+ searchText,
+ searchResults,
+ }),
+ );
- const options = this.getListData.map(threadInfo => (
+ const options = React.useMemo(() => {
+ return threads.map(threadInfo => (
));
+ }, [threads, createNewEntry]);
- return (
-
- {options}
-
- );
- }
-
- pickerDivRef = (pickerDiv: ?HTMLDivElement) => {
- this.pickerDiv = pickerDiv;
- };
-
- onPickerKeyDown = (event: SyntheticKeyboardEvent) => {
- if (event.keyCode === 27) {
- // Esc
- this.props.closePicker();
- }
- };
-
- onMouseDown = (event: SyntheticEvent) => {
- const target = htmlTargetFromEvent(event);
- invariant(this.pickerDiv, 'pickerDiv ref not set');
- if (this.pickerDiv.contains(target)) {
- // This prevents onBlur from firing
- event.preventDefault();
- }
- };
-
- onChangeSearchText = (searchText: string) => {
- const results = this.props.searchIndex.getSearchResults(searchText);
- this.setState({ searchText, searchResults: new Set(results) });
- };
+ return (
+
+ {options}
+
+ );
}
-const ConnectedThreadPicker: React.ComponentType = React.memo(
- function ConnectedThreadPicker(props) {
- const onScreenThreadInfos = useSelector(onScreenEntryEditableThreadInfos);
- const index = useSelector(state => threadSearchIndex(state));
- return (
-
- );
- },
-);
-
-export default ConnectedThreadPicker;
+export default ThreadPicker;