diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js
index cc26a2633..ddc7300fd 100644
--- a/frontend/.eslintrc.js
+++ b/frontend/.eslintrc.js
@@ -359,11 +359,16 @@ module.exports = {
],
rules: Object.assign(typescriptEslintRecommended.rules, {
- 'no-shadow': 'off',
- // These should be enabled after cleaning things up
- '@typescript-eslint/no-unused-vars': 'warn',
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ args: 'after-used',
+ argsIgnorePattern: '^_',
+ ignoreRestSiblings: true
+ }
+ ],
'@typescript-eslint/explicit-function-return-type': 'off',
- 'react/prop-types': 'off',
+ 'no-shadow': 'off',
'prettier/prettier': 'error',
'simple-import-sort/imports': [
'error',
@@ -376,7 +381,41 @@ module.exports = {
['^@?\\w', `^(${dirs})(/.*|$)`, '^\\.', '^\\..*css$']
]
}
- ]
+ ],
+
+ // React Hooks
+ 'react-hooks/rules-of-hooks': 'error',
+ 'react-hooks/exhaustive-deps': 'error',
+
+ // React
+ 'react/function-component-definition': 'error',
+ 'react/hook-use-state': 'error',
+ 'react/jsx-boolean-value': ['error', 'always'],
+ 'react/jsx-curly-brace-presence': [
+ 'error',
+ { props: 'never', children: 'never' }
+ ],
+ 'react/jsx-fragments': 'error',
+ 'react/jsx-handler-names': [
+ 'error',
+ {
+ eventHandlerPrefix: 'on',
+ eventHandlerPropPrefix: 'on'
+ }
+ ],
+ 'react/jsx-no-bind': ['error', { ignoreRefs: true }],
+ 'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }],
+ 'react/jsx-pascal-case': ['error', { allowAllCaps: true }],
+ 'react/jsx-sort-props': [
+ 'error',
+ {
+ callbacksLast: true,
+ noSortAlphabetically: true,
+ reservedFirst: true
+ }
+ ],
+ 'react/prop-types': 'off',
+ 'react/self-closing-comp': 'error'
})
},
{
diff --git a/frontend/src/App/ApplyTheme.tsx b/frontend/src/App/ApplyTheme.tsx
index cdd430541..df30f9ebb 100644
--- a/frontend/src/App/ApplyTheme.tsx
+++ b/frontend/src/App/ApplyTheme.tsx
@@ -1,13 +1,9 @@
-import React, { Fragment, ReactNode, useCallback, useEffect } from 'react';
+import { useCallback, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import themes from 'Styles/Themes';
import AppState from './State/AppState';
-interface ApplyThemeProps {
- children: ReactNode;
-}
-
function createThemeSelector() {
return createSelector(
(state: AppState) => state.settings.ui.item.theme || window.Radarr.theme,
@@ -17,7 +13,7 @@ function createThemeSelector() {
);
}
-function ApplyTheme({ children }: ApplyThemeProps) {
+function ApplyTheme() {
const theme = useSelector(createThemeSelector());
const updateCSSVariables = useCallback(() => {
@@ -31,7 +27,7 @@ function ApplyTheme({ children }: ApplyThemeProps) {
updateCSSVariables();
}, [updateCSSVariables, theme]);
- return {children};
+ return null;
}
export default ApplyTheme;
diff --git a/frontend/src/Components/Error/ErrorBoundaryError.tsx b/frontend/src/Components/Error/ErrorBoundaryError.tsx
index 6e9c16281..1a67d210c 100644
--- a/frontend/src/Components/Error/ErrorBoundaryError.tsx
+++ b/frontend/src/Components/Error/ErrorBoundaryError.tsx
@@ -63,7 +63,7 @@ function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
{info.componentStack}
)}
- {Version: {window.Radarr.version}
}
+ Version: {window.Radarr.version}
);
diff --git a/frontend/src/Helpers/Hooks/useModalOpenState.ts b/frontend/src/Helpers/Hooks/useModalOpenState.ts
index f5b5a96f0..24cffb2f1 100644
--- a/frontend/src/Helpers/Hooks/useModalOpenState.ts
+++ b/frontend/src/Helpers/Hooks/useModalOpenState.ts
@@ -3,15 +3,15 @@ import { useCallback, useState } from 'react';
export default function useModalOpenState(
initialState: boolean
): [boolean, () => void, () => void] {
- const [isOpen, setOpen] = useState(initialState);
+ const [isOpen, setIsOpen] = useState(initialState);
const setModalOpen = useCallback(() => {
- setOpen(true);
- }, [setOpen]);
+ setIsOpen(true);
+ }, [setIsOpen]);
const setModalClosed = useCallback(() => {
- setOpen(false);
- }, [setOpen]);
+ setIsOpen(false);
+ }, [setIsOpen]);
return [isOpen, setModalOpen, setModalClosed];
}
diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.tsx b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.tsx
index 0eea66249..ef9901969 100644
--- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.tsx
+++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.tsx
@@ -702,7 +702,7 @@ function InteractiveImportModalContent(
@@ -710,7 +710,7 @@ function InteractiveImportModalContent(
@@ -790,7 +790,7 @@ function InteractiveImportModalContent(
+
> = ({
- index,
- style,
- data,
-}) => {
+function Row({ index, style, data }: ListChildComponentProps) {
const { items, columns, onMovieSelect } = data;
+ const movie = index >= items.length ? null : items[index];
- if (index >= items.length) {
+ const handlePress = useCallback(() => {
+ if (movie?.id) {
+ onMovieSelect(movie.id);
+ }
+ }, [movie?.id, onMovieSelect]);
+
+ if (movie == null) {
return null;
}
- const movie = items[index];
-
return (
> = ({
justifyContent: 'space-between',
...style,
}}
- onPress={() => onMovieSelect(movie.id)}
+ onPress={handlePress}
>
> = ({
/>
);
-};
+}
function SelectMovieModalContent(props: SelectMovieModalContentProps) {
const { modalTitle, onMovieSelect, onModalClose } = props;
@@ -196,9 +197,9 @@ function SelectMovieModalContent(props: SelectMovieModalContentProps) {
/>
diff --git a/frontend/src/InteractiveSearch/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx b/frontend/src/InteractiveSearch/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx
index 81bf86e59..7d623decd 100644
--- a/frontend/src/InteractiveSearch/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx
+++ b/frontend/src/InteractiveSearch/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx
@@ -17,7 +17,7 @@ function SelectDownloadClientModal(props: SelectDownloadClientModalProps) {
props;
return (
-
+
> = ({
- index,
- style,
- data,
-}) => {
+function Row({ index, style, data }: ListChildComponentProps) {
const { items, ...otherData } = data;
if (index >= items.length) {
@@ -60,7 +56,7 @@ const Row: React.FC> = ({
);
-};
+}
function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;
diff --git a/frontend/src/Movie/Index/Posters/MovieIndexPosters.tsx b/frontend/src/Movie/Index/Posters/MovieIndexPosters.tsx
index ac14a8bfa..ebbece0de 100644
--- a/frontend/src/Movie/Index/Posters/MovieIndexPosters.tsx
+++ b/frontend/src/Movie/Index/Posters/MovieIndexPosters.tsx
@@ -60,12 +60,12 @@ const movieIndexSelector = createSelector(
}
);
-const Cell: React.FC> = ({
+function Cell({
columnIndex,
rowIndex,
style,
data,
-}) => {
+}: GridChildComponentProps) {
const { layout, items, sortKey, isSelectMode } = data;
const { columnCount, padding, posterWidth, posterHeight } = layout;
@@ -94,7 +94,7 @@ const Cell: React.FC> = ({
/>
);
-};
+}
function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;
diff --git a/frontend/src/Movie/Index/Select/Edit/EditMoviesModalContent.tsx b/frontend/src/Movie/Index/Select/Edit/EditMoviesModalContent.tsx
index c30215141..cf8c3ab7e 100644
--- a/frontend/src/Movie/Index/Select/Edit/EditMoviesModalContent.tsx
+++ b/frontend/src/Movie/Index/Select/Edit/EditMoviesModalContent.tsx
@@ -202,9 +202,7 @@ function EditMoviesModalContent(props: EditMoviesModalContentProps) {
includeNoChange={true}
includeNoChangeDisabled={false}
selectedValueOptions={{ includeFreeSpace: false }}
- helpText={
- 'Moving movies to the same root folder can be used to rename movie folders to match updated title or naming format'
- }
+ helpText="Moving movies to the same root folder can be used to rename movie folders to match updated title or naming format"
onChange={onInputChange}
/>
diff --git a/frontend/src/Movie/Index/Table/MovieIndexTable.tsx b/frontend/src/Movie/Index/Table/MovieIndexTable.tsx
index 03d6303d9..51d84d8df 100644
--- a/frontend/src/Movie/Index/Table/MovieIndexTable.tsx
+++ b/frontend/src/Movie/Index/Table/MovieIndexTable.tsx
@@ -44,11 +44,7 @@ const columnsSelector = createSelector(
(columns) => columns
);
-const Row: React.FC> = ({
- index,
- style,
- data,
-}) => {
+function Row({ index, style, data }: ListChildComponentProps) {
const { items, sortKey, columns, isSelectMode } = data;
if (index >= items.length) {
@@ -74,7 +70,7 @@ const Row: React.FC> = ({
/>
);
-};
+}
function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;
diff --git a/frontend/src/Movie/Index/Table/MovieIndexTableOptions.tsx b/frontend/src/Movie/Index/Table/MovieIndexTableOptions.tsx
index 3e3a6198a..5b968755a 100644
--- a/frontend/src/Movie/Index/Table/MovieIndexTableOptions.tsx
+++ b/frontend/src/Movie/Index/Table/MovieIndexTableOptions.tsx
@@ -1,4 +1,4 @@
-import React, { Fragment, useCallback } from 'react';
+import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -32,19 +32,17 @@ function MovieIndexTableOptions(props: MovieIndexTableOptionsProps) {
);
return (
-
-
- {translate('ShowSearch')}
-
-
-
-
+
+ {translate('ShowSearch')}
+
+
+
);
}
diff --git a/frontend/src/Parse/ParseToolbarButton.tsx b/frontend/src/Parse/ParseToolbarButton.tsx
index 43b8b959f..6dae456d5 100644
--- a/frontend/src/Parse/ParseToolbarButton.tsx
+++ b/frontend/src/Parse/ParseToolbarButton.tsx
@@ -1,4 +1,4 @@
-import React, { Fragment, useCallback, useState } from 'react';
+import React, { useCallback, useState } from 'react';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props';
import ParseModal from 'Parse/ParseModal';
@@ -16,7 +16,7 @@ function ParseToolbarButton() {
}, [setIsParseModalOpen]);
return (
-
+ <>
-
+ >
);
}
diff --git a/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx b/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx
index fee176554..cc02a2a9a 100644
--- a/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx
+++ b/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx
@@ -1,4 +1,4 @@
-import React, { Fragment } from 'react';
+import React from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import PageContent from 'Components/Page/PageContent';
@@ -17,11 +17,11 @@ function CustomFormatSettingsPage() {
// @ts-ignore
showSave={false}
additionalButtons={
-
+ <>
-
+ >
}
/>
diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx
index 2722f02fa..a788d824e 100644
--- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx
+++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx
@@ -231,9 +231,9 @@ function ManageDownloadClientsModalContent(
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
- onSelectAllChange={onSelectAllChange}
sortKey={sortKey}
sortDirection={sortDirection}
+ onSelectAllChange={onSelectAllChange}
onSortPress={onSortPress}
>
@@ -286,9 +286,9 @@ function ManageDownloadClientsModalContent(
@@ -281,9 +281,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {