From 5cac5b60681f31bd134b93fd28369b86c3c89563 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) Closes #10248 --- frontend/.eslintrc.js | 49 +++++++++++++++++-- frontend/src/App/ApplyTheme.tsx | 10 ++-- .../Components/Error/ErrorBoundaryError.tsx | 2 +- .../src/Helpers/Hooks/useModalOpenState.ts | 10 ++-- .../InteractiveImportModalContent.tsx | 6 +-- .../Language/SelectLanguageModal.tsx | 2 +- .../Movie/SelectMovieModalContent.tsx | 23 ++++----- .../SelectDownloadClientModal.tsx | 2 +- .../Index/Overview/MovieIndexOverviews.tsx | 8 +-- .../Movie/Index/Posters/MovieIndexPosters.tsx | 6 +-- .../Select/Edit/EditMoviesModalContent.tsx | 4 +- .../src/Movie/Index/Table/MovieIndexTable.tsx | 8 +-- .../Index/Table/MovieIndexTableOptions.tsx | 26 +++++----- frontend/src/Parse/ParseToolbarButton.tsx | 6 +-- .../CustomFormatSettingsPage.tsx | 6 +-- .../ManageDownloadClientsModalContent.tsx | 4 +- .../Manage/ManageImportListsModalContent.tsx | 2 +- .../Manage/ManageIndexersModalContent.tsx | 4 +- 18 files changed, 101 insertions(+), 77 deletions(-) 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) {