diff --git a/frontend/src/InteractiveSearch/InteractiveSearchFilterMenu.js b/frontend/src/InteractiveSearch/InteractiveSearchFilterMenu.js
deleted file mode 100644
index 1a3081ad1..000000000
--- a/frontend/src/InteractiveSearch/InteractiveSearchFilterMenu.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import FilterMenu from 'Components/Menu/FilterMenu';
-import PageMenuButton from 'Components/Menu/PageMenuButton';
-import { align } from 'Helpers/Props';
-import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
-import styles from './InteractiveSearch.css';
-
-function InteractiveSearchFilterMenu(props) {
- const {
- selectedFilterKey,
- filters,
- customFilters,
- onFilterSelect
- } = props;
-
- return (
-
-
-
- );
-}
-
-InteractiveSearchFilterMenu.propTypes = {
- selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
- filters: PropTypes.arrayOf(PropTypes.object).isRequired,
- customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
- onFilterSelect: PropTypes.func.isRequired
-};
-
-export default InteractiveSearchFilterMenu;
diff --git a/frontend/src/InteractiveSearch/InteractiveSearchFilterMenuConnector.js b/frontend/src/InteractiveSearch/InteractiveSearchFilterMenuConnector.js
deleted file mode 100644
index d3156aabc..000000000
--- a/frontend/src/InteractiveSearch/InteractiveSearchFilterMenuConnector.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-import { setReleasesFilter } from 'Store/Actions/releaseActions';
-import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
-import InteractiveSearchFilterMenu from './InteractiveSearchFilterMenu';
-
-function createMapStateToProps(appState) {
- return createSelector(
- createClientSideCollectionSelector('releases'),
- (releases) => {
- return {
- ...releases
- };
- }
- );
-}
-
-function createMapDispatchToProps(dispatch, props) {
- return {
- onFilterSelect(selectedFilterKey) {
- dispatch(setReleasesFilter({ selectedFilterKey }));
- }
- };
-}
-
-class InteractiveSearchFilterMenuConnector extends Component {
-
- //
- // Render
-
- render() {
- const {
- ...otherProps
- } = this.props;
-
- return (
-
-
- );
- }
-}
-
-export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchFilterMenuConnector);
diff --git a/frontend/src/Movie/Details/MovieDetails.js b/frontend/src/Movie/Details/MovieDetails.js
index aa0134ad3..e609a0b4f 100644
--- a/frontend/src/Movie/Details/MovieDetails.js
+++ b/frontend/src/Movie/Details/MovieDetails.js
@@ -27,7 +27,7 @@ import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import getMovieStatusDetails from 'Movie/getMovieStatusDetails';
import MovieHistoryModal from 'Movie/History/MovieHistoryModal';
import MoviePoster from 'Movie/MoviePoster';
-import MovieInteractiveSearchModalConnector from 'Movie/Search/MovieInteractiveSearchModalConnector';
+import MovieInteractiveSearchModal from 'Movie/Search/MovieInteractiveSearchModal';
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
@@ -740,7 +740,7 @@ class MovieDetails extends Component {
onModalClose={this.onInteractiveImportModalClose}
/>
- {
- this.setState({ isInteractiveSearchModalOpen: true });
- };
-
- onInteractiveSearchModalClose = () => {
- this.setState({ isInteractiveSearchModalOpen: false });
- };
-
- //
- // Render
-
- render() {
- const {
- movieId,
- movieTitle,
- isSearching,
- onSearchPress,
- ...otherProps
- } = this.props;
-
- return (
-
-
-
-
-
-
-
- );
- }
-}
-
-MovieSearchCell.propTypes = {
- movieId: PropTypes.number.isRequired,
- movieTitle: PropTypes.string.isRequired,
- isSearching: PropTypes.bool.isRequired,
- onSearchPress: PropTypes.func.isRequired
-};
-
-export default MovieSearchCell;
diff --git a/frontend/src/Movie/MovieSearchCell.tsx b/frontend/src/Movie/MovieSearchCell.tsx
new file mode 100644
index 000000000..cc7f07152
--- /dev/null
+++ b/frontend/src/Movie/MovieSearchCell.tsx
@@ -0,0 +1,71 @@
+import React, { useCallback } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { MOVIE_SEARCH } from 'Commands/commandNames';
+import IconButton from 'Components/Link/IconButton';
+import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
+import TableRowCell from 'Components/Table/Cells/TableRowCell';
+import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
+import { icons } from 'Helpers/Props';
+import { executeCommand } from 'Store/Actions/commandActions';
+import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
+import translate from 'Utilities/String/translate';
+import MovieInteractiveSearchModal from './Search/MovieInteractiveSearchModal';
+import styles from './MovieSearchCell.css';
+
+interface MovieSearchCellProps {
+ movieId: number;
+ movieTitle: string;
+}
+
+function MovieSearchCell(props: MovieSearchCellProps) {
+ const { movieId, movieTitle } = props;
+
+ const executingCommands = useSelector(createExecutingCommandsSelector());
+ const isSearching = executingCommands.some(({ name, body }) => {
+ const { movieIds = [] } = body;
+ return name === MOVIE_SEARCH && movieIds.indexOf(movieId) > -1;
+ });
+
+ const dispatch = useDispatch();
+
+ const [
+ isInteractiveSearchModalOpen,
+ setInteractiveSearchModalOpen,
+ setInteractiveSearchModalClosed,
+ ] = useModalOpenState(false);
+
+ const handleSearchPress = useCallback(() => {
+ dispatch(
+ executeCommand({
+ name: MOVIE_SEARCH,
+ movieIds: [movieId],
+ })
+ );
+ }, [movieId, dispatch]);
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export default MovieSearchCell;
diff --git a/frontend/src/Movie/MovieSearchCellConnector.js b/frontend/src/Movie/MovieSearchCellConnector.js
deleted file mode 100644
index 0d8bc34e4..000000000
--- a/frontend/src/Movie/MovieSearchCellConnector.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-import * as commandNames from 'Commands/commandNames';
-import MovieSearchCell from 'Movie/MovieSearchCell';
-import { executeCommand } from 'Store/Actions/commandActions';
-import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
-import createMovieSelector from 'Store/Selectors/createMovieSelector';
-import { isCommandExecuting } from 'Utilities/Command';
-
-function createMapStateToProps() {
- return createSelector(
- (state, { movieId }) => movieId,
- createMovieSelector(),
- createCommandsSelector(),
- (movieId, movie, commands) => {
- const isSearching = commands.some((command) => {
- const movieSearch = command.name === commandNames.MOVIE_SEARCH;
-
- if (!movieSearch) {
- return false;
- }
-
- return (
- isCommandExecuting(command) &&
- command.body.movieIds.indexOf(movieId) > -1
- );
- });
-
- return {
- movieMonitored: movie.monitored,
- isSearching
- };
- }
- );
-}
-
-function createMapDispatchToProps(dispatch, props) {
- return {
- onSearchPress(name, path) {
- dispatch(executeCommand({
- name: commandNames.MOVIE_SEARCH,
- movieIds: [props.movieId]
- }));
- }
- };
-}
-
-export default connect(createMapStateToProps, createMapDispatchToProps)(MovieSearchCell);
diff --git a/frontend/src/Movie/Search/MovieInteractiveSearchModal.js b/frontend/src/Movie/Search/MovieInteractiveSearchModal.js
deleted file mode 100644
index b381ac563..000000000
--- a/frontend/src/Movie/Search/MovieInteractiveSearchModal.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import Modal from 'Components/Modal/Modal';
-import { sizes } from 'Helpers/Props';
-import MovieInteractiveSearchModalContent from './MovieInteractiveSearchModalContent';
-
-function MovieInteractiveSearchModal(props) {
- const {
- isOpen,
- movieId,
- movieTitle,
- onModalClose
- } = props;
-
- return (
-
-
-
- );
-}
-
-MovieInteractiveSearchModal.propTypes = {
- isOpen: PropTypes.bool.isRequired,
- movieId: PropTypes.number.isRequired,
- movieTitle: PropTypes.string,
- onModalClose: PropTypes.func.isRequired
-};
-
-export default MovieInteractiveSearchModal;
diff --git a/frontend/src/Movie/Search/MovieInteractiveSearchModal.tsx b/frontend/src/Movie/Search/MovieInteractiveSearchModal.tsx
new file mode 100644
index 000000000..5a4fb3a09
--- /dev/null
+++ b/frontend/src/Movie/Search/MovieInteractiveSearchModal.tsx
@@ -0,0 +1,46 @@
+import React, { useCallback } from 'react';
+import { useDispatch } from 'react-redux';
+import Modal from 'Components/Modal/Modal';
+import { sizes } from 'Helpers/Props';
+import {
+ cancelFetchReleases,
+ clearReleases,
+} from 'Store/Actions/releaseActions';
+import MovieInteractiveSearchModalContent from './MovieInteractiveSearchModalContent';
+
+interface MovieInteractiveSearchModalProps {
+ isOpen: boolean;
+ movieId: number;
+ movieTitle?: string;
+ onModalClose(): void;
+}
+
+function MovieInteractiveSearchModal(props: MovieInteractiveSearchModalProps) {
+ const { isOpen, movieId, movieTitle, onModalClose } = props;
+
+ const dispatch = useDispatch();
+
+ const handleModalClose = useCallback(() => {
+ dispatch(cancelFetchReleases());
+ dispatch(clearReleases());
+
+ onModalClose();
+ }, [dispatch, onModalClose]);
+
+ return (
+
+
+
+ );
+}
+
+export default MovieInteractiveSearchModal;
diff --git a/frontend/src/Movie/Search/MovieInteractiveSearchModalConnector.js b/frontend/src/Movie/Search/MovieInteractiveSearchModalConnector.js
deleted file mode 100644
index 9f53b712f..000000000
--- a/frontend/src/Movie/Search/MovieInteractiveSearchModalConnector.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
-import MovieInteractiveSearchModal from './MovieInteractiveSearchModal';
-
-function createMapDispatchToProps(dispatch, props) {
- return {
- dispatchCancelFetchReleases() {
- dispatch(cancelFetchReleases());
- },
-
- dispatchClearReleases() {
- dispatch(clearReleases());
- },
-
- onModalClose() {
- dispatch(cancelFetchReleases());
- dispatch(clearReleases());
- props.onModalClose();
- }
- };
-}
-
-class MovieInteractiveSearchModalConnector extends Component {
-
- //
- // Lifecycle
-
- componentWillUnmount() {
- this.props.dispatchCancelFetchReleases();
- this.props.dispatchClearReleases();
- }
-
- //
- // Render
-
- render() {
- const {
- dispatchCancelFetchReleases,
- dispatchClearReleases,
- ...otherProps
- } = this.props;
-
- return (
-
- );
- }
-}
-
-MovieInteractiveSearchModalConnector.propTypes = {
- ...MovieInteractiveSearchModal.propTypes,
- dispatchCancelFetchReleases: PropTypes.func.isRequired,
- dispatchClearReleases: PropTypes.func.isRequired
-};
-
-export default connect(null, createMapDispatchToProps)(MovieInteractiveSearchModalConnector);
diff --git a/frontend/src/Movie/Search/MovieInteractiveSearchModalContent.tsx b/frontend/src/Movie/Search/MovieInteractiveSearchModalContent.tsx
index 4fbca8b47..a5a9db2e2 100644
--- a/frontend/src/Movie/Search/MovieInteractiveSearchModalContent.tsx
+++ b/frontend/src/Movie/Search/MovieInteractiveSearchModalContent.tsx
@@ -1,4 +1,5 @@
-import React from 'react';
+import React, { useEffect } from 'react';
+import { useDispatch } from 'react-redux';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
@@ -6,6 +7,10 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { scrollDirections } from 'Helpers/Props';
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
+import {
+ cancelFetchReleases,
+ clearReleases,
+} from 'Store/Actions/releaseActions';
import translate from 'Utilities/String/translate';
interface MovieInteractiveSearchModalContentProps {
@@ -19,6 +24,15 @@ function MovieInteractiveSearchModalContent(
) {
const { movieId, movieTitle, onModalClose } = props;
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ return () => {
+ dispatch(cancelFetchReleases());
+ dispatch(clearReleases());
+ };
+ }, [dispatch]);
+
return (
diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js
index 31a4c50fb..e8e073a41 100644
--- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js
+++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js
@@ -5,7 +5,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import movieEntities from 'Movie/movieEntities';
-import MovieSearchCellConnector from 'Movie/MovieSearchCellConnector';
+import MovieSearchCell from 'Movie/MovieSearchCell';
import MovieStatusConnector from 'Movie/MovieStatusConnector';
import MovieTitleLink from 'Movie/MovieTitleLink';
import MovieFileLanguageConnector from 'MovieFile/MovieFileLanguageConnector';
@@ -124,7 +124,7 @@ function CutoffUnmetRow(props) {
if (name === 'actions') {
return (
-