diff --git a/web/calendar/thread-picker.react.js b/web/calendar/thread-picker.react.js index 7f2524890..3c6f09e91 100644 --- a/web/calendar/thread-picker.react.js +++ b/web/calendar/thread-picker.react.js @@ -1,194 +1,191 @@ // @flow import invariant from 'invariant'; -import PropTypes from 'prop-types'; import * as React from 'react'; import { onScreenEntryEditableThreadInfos } from 'lib/selectors/thread-selectors'; import type { ThreadInfo } from 'lib/types/thread-types'; -import { threadInfoPropType } from 'lib/types/thread-types'; -import { connect } from 'lib/utils/redux-utils'; -import type { AppState } from '../redux/redux-setup'; +import { useSelector } from '../redux/redux-utils'; import { htmlTargetFromEvent } from '../vector-utils'; import { LeftPager, RightPager } from '../vectors.react'; import css from './thread-picker.css'; -type OptionProps = { - threadInfo: ThreadInfo, - createNewEntry: (threadID: string) => void, -}; -class ThreadPickerOption extends React.PureComponent { - render() { - const colorStyle = { backgroundColor: `#${this.props.threadInfo.color}` }; - return ( -
- -
- {this.props.threadInfo.uiName} - -
- ); - } - - onClick = () => { - this.props.createNewEntry(this.props.threadInfo.id); - }; +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 Props = { - onScreenThreadInfos: ThreadInfo[], - createNewEntry: (threadID: string) => void, - closePicker: () => void, -}; -type State = { - currentPage: number, -}; +type BaseProps = {| + +createNewEntry: (threadID: string) => void, + +closePicker: () => void, +|}; +type Props = {| + ...BaseProps, + +onScreenThreadInfos: $ReadOnlyArray, +|}; +type State = {| + +currentPage: number, +|}; class ThreadPicker extends React.PureComponent { static pageSize = 5; pickerDiv: ?HTMLDivElement; constructor(props: Props) { super(props); this.state = { currentPage: 0, }; invariant( props.onScreenThreadInfos.length > 0, "ThreadPicker can't be open when onScreenThreadInfos is empty", ); } componentDidMount() { invariant(this.pickerDiv, 'pickerDiv ref unset'); this.pickerDiv.focus(); } render() { const length = this.props.onScreenThreadInfos.length; invariant( length > 0, "ThreadPicker can't be open when onScreenThreadInfos is empty", ); const firstIndex = ThreadPicker.pageSize * this.state.currentPage; const secondIndex = Math.min( ThreadPicker.pageSize * (this.state.currentPage + 1), length, ); let pager = null; if (length > ThreadPicker.pageSize) { let leftPager = ; if (this.state.currentPage > 0) { leftPager = ( {leftPager} ); } let rightPager = ; if (ThreadPicker.pageSize * (this.state.currentPage + 1) < length) { rightPager = ( {rightPager} ); } pager = (
{leftPager} {`${firstIndex + 1}–${secondIndex} of ${length}`} {rightPager}
); } const options = this.props.onScreenThreadInfos .slice(firstIndex, secondIndex) .map((threadInfo) => ( )); return (
{options} {pager}
); } 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(); } }; onBackPagerClick = (event: SyntheticEvent) => { event.preventDefault(); this.setState((prevState) => { invariant(prevState.currentPage > 0, "can't go back from 0"); return { currentPage: prevState.currentPage - 1 }; }); }; onNextPagerClick = (event: SyntheticEvent) => { event.preventDefault(); this.setState((prevState, props) => { invariant( ThreadPicker.pageSize * (prevState.currentPage + 1) < props.onScreenThreadInfos.length, 'page is too high', ); return { currentPage: prevState.currentPage + 1 }; }); }; } -ThreadPicker.propTypes = { - onScreenThreadInfos: PropTypes.arrayOf(threadInfoPropType).isRequired, - createNewEntry: PropTypes.func.isRequired, - closePicker: PropTypes.func.isRequired, -}; - -export default connect((state: AppState) => ({ - onScreenThreadInfos: onScreenEntryEditableThreadInfos(state), -}))(ThreadPicker); +export default React.memo(function ConnectedThreadPicker( + props: BaseProps, +) { + const onScreenThreadInfos = useSelector(onScreenEntryEditableThreadInfos); + return ; +});