diff --git a/web/modals/history/history-entry.react.js b/web/modals/history/history-entry.react.js index 3de625eba..111953574 100644 --- a/web/modals/history/history-entry.react.js +++ b/web/modals/history/history-entry.react.js @@ -1,179 +1,181 @@ // @flow import classNames from 'classnames'; import invariant from 'invariant'; -import PropTypes from 'prop-types'; import * as React from 'react'; import { restoreEntryActionTypes, restoreEntry, } from 'lib/actions/entry-actions'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; import { threadInfoSelector } from 'lib/selectors/thread-selectors'; import { colorIsDark } from 'lib/shared/thread-utils'; import { type EntryInfo, - entryInfoPropType, type RestoreEntryInfo, type RestoreEntryResponse, type CalendarQuery, } from 'lib/types/entry-types'; import type { LoadingStatus } from 'lib/types/loading-types'; -import { threadInfoPropType } from 'lib/types/thread-types'; import type { ThreadInfo } from 'lib/types/thread-types'; -import type { DispatchActionPromise } from 'lib/utils/action-utils'; -import { connect } from 'lib/utils/redux-utils'; +import { + type DispatchActionPromise, + useDispatchActionPromise, + useServerCall, +} from 'lib/utils/action-utils'; import LoadingIndicator from '../../loading-indicator.react'; -import type { AppState } from '../../redux/redux-setup'; +import { useSelector } from '../../redux/redux-utils'; import { nonThreadCalendarQuery } from '../../selectors/nav-selectors'; import css from './history.css'; -type Props = { - entryInfo: EntryInfo, - onClick: (entryID: string) => void, - animateAndLoadEntry: (entryID: string) => void, - // Redux state - threadInfo: ThreadInfo, - loggedIn: boolean, - restoreLoadingStatus: LoadingStatus, - calendarQuery: () => CalendarQuery, - // Redux dispatch functions - dispatchActionPromise: DispatchActionPromise, - // async functions that hit server APIs - restoreEntry: (info: RestoreEntryInfo) => Promise, -}; +type BaseProps = {| + +entryInfo: EntryInfo, + +onClick: (entryID: string) => void, + +animateAndLoadEntry: (entryID: string) => void, +|}; +type Props = {| + ...BaseProps, + +threadInfo: ThreadInfo, + +loggedIn: boolean, + +restoreLoadingStatus: LoadingStatus, + +calendarQuery: () => CalendarQuery, + +dispatchActionPromise: DispatchActionPromise, + +restoreEntry: (info: RestoreEntryInfo) => Promise, +|}; class HistoryEntry extends React.PureComponent { render() { let deleted = null; if (this.props.entryInfo.deleted) { let restore = null; if (this.props.loggedIn) { restore = ( ( restore ) ); } deleted = ( deleted {restore} ); } const textClasses = classNames({ [css.entry]: true, [css.darkEntry]: colorIsDark(this.props.threadInfo.color), }); const textStyle = { backgroundColor: '#' + this.props.threadInfo.color }; const creator = this.props.entryInfo.creator === null ? ( 'Anonymous' ) : ( {this.props.entryInfo.creator} ); return (
  • {this.props.entryInfo.text}
    {'created by '} {creator} {this.props.threadInfo.uiName}
    {deleted} revision history >
  • ); } onRestore = (event: SyntheticEvent) => { event.preventDefault(); const entryID = this.props.entryInfo.id; invariant(entryID, 'entryInfo.id (serverID) should be set'); this.props.dispatchActionPromise( restoreEntryActionTypes, this.restoreEntryAction(), { customKeyName: `${restoreEntryActionTypes.started}:${entryID}` }, ); }; onClick = (event: SyntheticEvent) => { event.preventDefault(); const entryID = this.props.entryInfo.id; invariant(entryID, 'entryInfo.id (serverID) should be set'); this.props.onClick(entryID); }; async restoreEntryAction() { const entryID = this.props.entryInfo.id; invariant(entryID, 'entry should have ID'); const result = await this.props.restoreEntry({ entryID, calendarQuery: this.props.calendarQuery(), }); this.props.animateAndLoadEntry(entryID); return { ...result, threadID: this.props.threadInfo.id }; } } -HistoryEntry.propTypes = { - entryInfo: entryInfoPropType, - onClick: PropTypes.func.isRequired, - animateAndLoadEntry: PropTypes.func.isRequired, - threadInfo: threadInfoPropType, - loggedIn: PropTypes.bool.isRequired, - restoreLoadingStatus: PropTypes.string.isRequired, - calendarQuery: PropTypes.func.isRequired, - dispatchActionPromise: PropTypes.func.isRequired, - restoreEntry: PropTypes.func.isRequired, -}; +export default React.memo(function ConnectedHistoryEntry( + props: BaseProps, +) { + const entryID = props.entryInfo.id; + invariant(entryID, 'entryInfo.id (serverID) should be set'); + const threadInfo = useSelector( + (state) => threadInfoSelector(state)[props.entryInfo.threadID], + ); + const loggedIn = useSelector( + (state) => + !!(state.currentUserInfo && !state.currentUserInfo.anonymous && true), + ); + const restoreLoadingStatus = useSelector( + createLoadingStatusSelector( + restoreEntryActionTypes, + `${restoreEntryActionTypes.started}:${entryID}`, + ), + ); + const calanderQuery = useSelector(nonThreadCalendarQuery); + const callRestoreEntry = useServerCall(restoreEntry); + const dispatchActionPromise = useDispatchActionPromise(); -export default connect( - (state: AppState, ownProps: { entryInfo: EntryInfo }) => { - const entryID = ownProps.entryInfo.id; - invariant(entryID, 'entryInfo.id (serverID) should be set'); - return { - threadInfo: threadInfoSelector(state)[ownProps.entryInfo.threadID], - loggedIn: !!( - state.currentUserInfo && - !state.currentUserInfo.anonymous && - true - ), - restoreLoadingStatus: createLoadingStatusSelector( - restoreEntryActionTypes, - `${restoreEntryActionTypes.started}:${entryID}`, - )(state), - calendarQuery: nonThreadCalendarQuery(state), - }; - }, - { restoreEntry }, -)(HistoryEntry); + return ( + + ); +});