From 5cbbd060a43be1dac97e5c1b4695f504cf3f3d10 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 30 Jul 2024 17:44:39 -0700 Subject: [PATCH] Update React Lint rules for TSX (cherry picked from commit 1299a97579bec52ee3d16ab8d05c9e22edd80330) --- frontend/.eslintrc.js | 49 +++++++++++++++++-- frontend/src/App/AppRoutes.tsx | 8 +-- .../Components/Error/ErrorBoundaryError.tsx | 6 +-- .../src/Helpers/Hooks/useModalOpenState.ts | 10 ++-- frontend/src/Indexer/Add/AddIndexerModal.tsx | 2 +- .../Indexer/Add/AddIndexerModalContent.tsx | 44 ++++++++++++----- .../Indexer/Index/Table/CapabilitiesLabel.tsx | 3 +- .../Indexer/Index/Table/IndexerIndexTable.tsx | 8 +-- .../Index/Table/IndexerIndexTableOptions.tsx | 26 +++++----- .../Indexer/Index/Table/IndexerStatusCell.tsx | 14 +++--- .../Info/History/IndexerHistoryRow.tsx | 2 +- frontend/src/Indexer/NoIndexer.tsx | 2 +- frontend/src/Indexer/Stats/IndexerStats.tsx | 2 +- .../SelectDownloadClientModal.tsx | 2 +- frontend/src/Search/Table/SearchIndexRow.tsx | 2 +- .../Applications/ApplicationSettings.tsx | 6 +-- .../Manage/ManageApplicationsModalContent.tsx | 4 +- .../ManageDownloadClientsModalContent.tsx | 4 +- frontend/src/System/Status/About/About.tsx | 21 ++++---- src/NzbDrone.Core/Localization/Core/en.json | 1 + 20 files changed, 134 insertions(+), 82 deletions(-) diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index 8d844cb8b..56eaaeaab 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -357,11 +357,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', @@ -374,7 +379,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/AppRoutes.tsx b/frontend/src/App/AppRoutes.tsx index ff0002b51..8b16e19a3 100644 --- a/frontend/src/App/AppRoutes.tsx +++ b/frontend/src/App/AppRoutes.tsx @@ -23,6 +23,10 @@ import Tasks from 'System/Tasks/Tasks'; import UpdatesConnector from 'System/Updates/UpdatesConnector'; import getPathWithUrlBase from 'Utilities/getPathWithUrlBase'; +function RedirectWithUrlBase() { + return ; +} + function AppRoutes() { return ( @@ -39,9 +43,7 @@ function AppRoutes() { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore addUrlBase={false} - render={() => { - return ; - }} + render={RedirectWithUrlBase} /> )} diff --git a/frontend/src/Components/Error/ErrorBoundaryError.tsx b/frontend/src/Components/Error/ErrorBoundaryError.tsx index 022cf5a45..51d286311 100644 --- a/frontend/src/Components/Error/ErrorBoundaryError.tsx +++ b/frontend/src/Components/Error/ErrorBoundaryError.tsx @@ -63,11 +63,7 @@ function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
{info.componentStack}
)} - { -
- Version: {window.Prowlarr.version} -
- } +
Version: {window.Prowlarr.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/Indexer/Add/AddIndexerModal.tsx b/frontend/src/Indexer/Add/AddIndexerModal.tsx index 337881e16..be22eec57 100644 --- a/frontend/src/Indexer/Add/AddIndexerModal.tsx +++ b/frontend/src/Indexer/Add/AddIndexerModal.tsx @@ -29,8 +29,8 @@ function AddIndexerModal({ { + setFilterProtocols(value); + }, + [setFilterProtocols] + ); + + const onFilterLanguagesChange = useCallback( + ({ value }: { value: string[] }) => { + setFilterLanguages(value); + }, + [setFilterLanguages] + ); + + const onFilterPrivacyLevelsChange = useCallback( + ({ value }: { value: string[] }) => { + setFilterPrivacyLevels(value); + }, + [setFilterPrivacyLevels] + ); + + const onFilterCategoriesChange = useCallback( + ({ value }: { value: number[] }) => { + setFilterCategories(value); + }, + [setFilterCategories] + ); + const onIndexerSelect = useCallback( ({ implementation, @@ -304,9 +332,7 @@ function AddIndexerModalContent(props: AddIndexerModalContentProps) { name="indexerProtocols" value={filterProtocols} values={PROTOCOLS} - onChange={({ value }: { value: string[] }) => - setFilterProtocols(value) - } + onChange={onFilterProtocolsChange} /> @@ -319,9 +345,7 @@ function AddIndexerModalContent(props: AddIndexerModalContentProps) { name="indexerLanguages" value={filterLanguages} values={languages} - onChange={({ value }: { value: string[] }) => - setFilterLanguages(value) - } + onChange={onFilterLanguagesChange} /> @@ -331,9 +355,7 @@ function AddIndexerModalContent(props: AddIndexerModalContentProps) { name="indexerPrivacyLevels" value={filterPrivacyLevels} values={PRIVACY_LEVELS} - onChange={({ value }: { value: string[] }) => - setFilterPrivacyLevels(value) - } + onChange={onFilterPrivacyLevelsChange} /> @@ -345,9 +367,7 @@ function AddIndexerModalContent(props: AddIndexerModalContentProps) { - setFilterCategories(value) - } + onChange={onFilterCategoriesChange} /> diff --git a/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx b/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx index c832806ed..8e30532cc 100644 --- a/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx +++ b/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx @@ -2,6 +2,7 @@ import { uniqBy } from 'lodash'; import React from 'react'; import Label from 'Components/Label'; import { IndexerCapabilities } from 'Indexer/Indexer'; +import translate from 'Utilities/String/translate'; interface CapabilitiesLabelProps { capabilities: IndexerCapabilities; @@ -38,7 +39,7 @@ function CapabilitiesLabel(props: CapabilitiesLabelProps) { ); })} - {filteredList.length === 0 ? : null} + {filteredList.length === 0 ? : null} ); } diff --git a/frontend/src/Indexer/Index/Table/IndexerIndexTable.tsx b/frontend/src/Indexer/Index/Table/IndexerIndexTable.tsx index ee717317a..be5e78195 100644 --- a/frontend/src/Indexer/Index/Table/IndexerIndexTable.tsx +++ b/frontend/src/Indexer/Index/Table/IndexerIndexTable.tsx @@ -46,11 +46,7 @@ const columnsSelector = createSelector( (columns) => columns ); -const Row: React.FC> = ({ - index, - style, - data, -}) => { +function Row({ index, style, data }: ListChildComponentProps) { const { items, sortKey, columns, isSelectMode, onCloneIndexerPress } = data; if (index >= items.length) { @@ -77,7 +73,7 @@ const Row: React.FC> = ({ /> ); -}; +} function getWindowScrollTopPosition() { return document.documentElement.scrollTop || document.body.scrollTop || 0; diff --git a/frontend/src/Indexer/Index/Table/IndexerIndexTableOptions.tsx b/frontend/src/Indexer/Index/Table/IndexerIndexTableOptions.tsx index 6155929df..3aa087790 100644 --- a/frontend/src/Indexer/Index/Table/IndexerIndexTableOptions.tsx +++ b/frontend/src/Indexer/Index/Table/IndexerIndexTableOptions.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 IndexerIndexTableOptions(props: IndexerIndexTableOptionsProps) { ); return ( - - - {translate('ShowSearch')} - - - - + + {translate('ShowSearch')} + + + ); } diff --git a/frontend/src/Indexer/Index/Table/IndexerStatusCell.tsx b/frontend/src/Indexer/Index/Table/IndexerStatusCell.tsx index a3d694e9d..8a05600df 100644 --- a/frontend/src/Indexer/Index/Table/IndexerStatusCell.tsx +++ b/frontend/src/Indexer/Index/Table/IndexerStatusCell.tsx @@ -38,14 +38,12 @@ function IndexerStatusCell(props: IndexerStatusCellProps) { return ( - { - - } + {status ? ( diff --git a/frontend/src/Indexer/NoIndexer.tsx b/frontend/src/Indexer/NoIndexer.tsx index f71ffc6e9..bf5afa1fe 100644 --- a/frontend/src/Indexer/NoIndexer.tsx +++ b/frontend/src/Indexer/NoIndexer.tsx @@ -28,7 +28,7 @@ function NoIndexer(props: NoIndexerProps) {
-
diff --git a/frontend/src/Indexer/Stats/IndexerStats.tsx b/frontend/src/Indexer/Stats/IndexerStats.tsx index 0ca17f785..8f09475f0 100644 --- a/frontend/src/Indexer/Stats/IndexerStats.tsx +++ b/frontend/src/Indexer/Stats/IndexerStats.tsx @@ -230,9 +230,9 @@ function IndexerStats() { selectedFilterKey={selectedFilterKey} filters={filters} customFilters={customFilters} - onFilterSelect={onFilterSelect} filterModalConnectorComponent={IndexerStatsFilterModal} isDisabled={false} + onFilterSelect={onFilterSelect} /> diff --git a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx b/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx index 81bf86e59..7d623decd 100644 --- a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx +++ b/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx @@ -17,7 +17,7 @@ function SelectDownloadClientModal(props: SelectDownloadClientModalProps) { props; return ( - + + <> - + } /> diff --git a/frontend/src/Settings/Applications/Applications/Manage/ManageApplicationsModalContent.tsx b/frontend/src/Settings/Applications/Applications/Manage/ManageApplicationsModalContent.tsx index e2c36529c..bb81729f3 100644 --- a/frontend/src/Settings/Applications/Applications/Manage/ManageApplicationsModalContent.tsx +++ b/frontend/src/Settings/Applications/Applications/Manage/ManageApplicationsModalContent.tsx @@ -213,9 +213,9 @@ function ManageApplicationsModalContent( selectAll={true} allSelected={allSelected} allUnselected={allUnselected} - onSelectAllChange={onSelectAllChange} sortKey={sortKey} sortDirection={sortDirection} + onSelectAllChange={onSelectAllChange} onSortPress={onSortPress} > @@ -268,9 +268,9 @@ function ManageApplicationsModalContent( @@ -233,9 +233,9 @@ function ManageDownloadClientsModalContent( - {' '} - {packageVersion} {' by '}{' '} - {' '} - + ) : ( packageVersion ) @@ -57,16 +58,16 @@ function About() { /> )} - {isNetCore && ( + {isNetCore ? ( - )} + ) : null} - {isDocker && ( - - )} + {isDocker ? ( + + ) : null}