Update React Lint rules for TSX

(cherry picked from commit 1299a97579bec52ee3d16ab8d05c9e22edd80330)

Closes #10248
pull/10296/head
Mark McDowall 4 months ago committed by Bogdan
parent 4628868dfa
commit 5cac5b6068

@ -359,11 +359,16 @@ module.exports = {
], ],
rules: Object.assign(typescriptEslintRecommended.rules, { rules: Object.assign(typescriptEslintRecommended.rules, {
'no-shadow': 'off', '@typescript-eslint/no-unused-vars': [
// These should be enabled after cleaning things up 'error',
'@typescript-eslint/no-unused-vars': 'warn', {
args: 'after-used',
argsIgnorePattern: '^_',
ignoreRestSiblings: true
}
],
'@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'react/prop-types': 'off', 'no-shadow': 'off',
'prettier/prettier': 'error', 'prettier/prettier': 'error',
'simple-import-sort/imports': [ 'simple-import-sort/imports': [
'error', 'error',
@ -376,7 +381,41 @@ module.exports = {
['^@?\\w', `^(${dirs})(/.*|$)`, '^\\.', '^\\..*css$'] ['^@?\\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'
}) })
}, },
{ {

@ -1,13 +1,9 @@
import React, { Fragment, ReactNode, useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import themes from 'Styles/Themes'; import themes from 'Styles/Themes';
import AppState from './State/AppState'; import AppState from './State/AppState';
interface ApplyThemeProps {
children: ReactNode;
}
function createThemeSelector() { function createThemeSelector() {
return createSelector( return createSelector(
(state: AppState) => state.settings.ui.item.theme || window.Radarr.theme, (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 theme = useSelector(createThemeSelector());
const updateCSSVariables = useCallback(() => { const updateCSSVariables = useCallback(() => {
@ -31,7 +27,7 @@ function ApplyTheme({ children }: ApplyThemeProps) {
updateCSSVariables(); updateCSSVariables();
}, [updateCSSVariables, theme]); }, [updateCSSVariables, theme]);
return <Fragment>{children}</Fragment>; return null;
} }
export default ApplyTheme; export default ApplyTheme;

@ -63,7 +63,7 @@ function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
<div>{info.componentStack}</div> <div>{info.componentStack}</div>
)} )}
{<div className={styles.version}>Version: {window.Radarr.version}</div>} <div className={styles.version}>Version: {window.Radarr.version}</div>
</details> </details>
</div> </div>
); );

@ -3,15 +3,15 @@ import { useCallback, useState } from 'react';
export default function useModalOpenState( export default function useModalOpenState(
initialState: boolean initialState: boolean
): [boolean, () => void, () => void] { ): [boolean, () => void, () => void] {
const [isOpen, setOpen] = useState(initialState); const [isOpen, setIsOpen] = useState(initialState);
const setModalOpen = useCallback(() => { const setModalOpen = useCallback(() => {
setOpen(true); setIsOpen(true);
}, [setOpen]); }, [setIsOpen]);
const setModalClosed = useCallback(() => { const setModalClosed = useCallback(() => {
setOpen(false); setIsOpen(false);
}, [setOpen]); }, [setIsOpen]);
return [isOpen, setModalOpen, setModalClosed]; return [isOpen, setModalOpen, setModalClosed];
} }

@ -702,7 +702,7 @@ function InteractiveImportModalContent(
<MenuContent> <MenuContent>
<SelectedMenuItem <SelectedMenuItem
name={'all'} name="all"
isSelected={!filterExistingFiles} isSelected={!filterExistingFiles}
onPress={onFilterExistingFilesChange} onPress={onFilterExistingFilesChange}
> >
@ -710,7 +710,7 @@ function InteractiveImportModalContent(
</SelectedMenuItem> </SelectedMenuItem>
<SelectedMenuItem <SelectedMenuItem
name={'new'} name="new"
isSelected={filterExistingFiles} isSelected={filterExistingFiles}
onPress={onFilterExistingFilesChange} onPress={onFilterExistingFilesChange}
> >
@ -790,7 +790,7 @@ function InteractiveImportModalContent(
<SelectInput <SelectInput
className={styles.bulkSelect} className={styles.bulkSelect}
name="select" name="select"
value={'select'} value="select"
values={bulkSelectOptions} values={bulkSelectOptions}
isDisabled={!selectedIds.length} isDisabled={!selectedIds.length}
onChange={onSelectModalSelect} onChange={onSelectModalSelect}

@ -17,7 +17,7 @@ function SelectLanguageModal(props: SelectLanguageModalProps) {
props; props;
return ( return (
<Modal isOpen={isOpen} onModalClose={onModalClose} size={sizes.MEDIUM}> <Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
<SelectLanguageModalContent <SelectLanguageModalContent
languageIds={languageIds} languageIds={languageIds}
modalTitle={modalTitle} modalTitle={modalTitle}

@ -64,19 +64,20 @@ interface RowItemData {
onMovieSelect(movieId: number): void; onMovieSelect(movieId: number): void;
} }
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
index,
style,
data,
}) => {
const { items, columns, onMovieSelect } = data; 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; return null;
} }
const movie = items[index];
return ( return (
<VirtualTableRowButton <VirtualTableRowButton
style={{ style={{
@ -84,7 +85,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
justifyContent: 'space-between', justifyContent: 'space-between',
...style, ...style,
}} }}
onPress={() => onMovieSelect(movie.id)} onPress={handlePress}
> >
<SelectMovieRow <SelectMovieRow
id={movie.id} id={movie.id}
@ -97,7 +98,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
/> />
</VirtualTableRowButton> </VirtualTableRowButton>
); );
}; }
function SelectMovieModalContent(props: SelectMovieModalContentProps) { function SelectMovieModalContent(props: SelectMovieModalContentProps) {
const { modalTitle, onMovieSelect, onModalClose } = props; const { modalTitle, onMovieSelect, onModalClose } = props;
@ -196,9 +197,9 @@ function SelectMovieModalContent(props: SelectMovieModalContentProps) {
/> />
<Scroller <Scroller
ref={scrollerRef}
className={styles.scroller} className={styles.scroller}
autoFocus={false} autoFocus={false}
ref={scrollerRef}
> >
<SelectMovieModalTableHeader columns={columns} /> <SelectMovieModalTableHeader columns={columns} />
<List<RowItemData> <List<RowItemData>

@ -17,7 +17,7 @@ function SelectDownloadClientModal(props: SelectDownloadClientModalProps) {
props; props;
return ( return (
<Modal isOpen={isOpen} onModalClose={onModalClose} size={sizes.MEDIUM}> <Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
<SelectDownloadClientModalContent <SelectDownloadClientModalContent
protocol={protocol} protocol={protocol}
modalTitle={modalTitle} modalTitle={modalTitle}

@ -42,11 +42,7 @@ interface MovieIndexOverviewsProps {
isSmallScreen: boolean; isSmallScreen: boolean;
} }
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
index,
style,
data,
}) => {
const { items, ...otherData } = data; const { items, ...otherData } = data;
if (index >= items.length) { if (index >= items.length) {
@ -60,7 +56,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
<MovieIndexOverview movieId={movie.id} {...otherData} /> <MovieIndexOverview movieId={movie.id} {...otherData} />
</div> </div>
); );
}; }
function getWindowScrollTopPosition() { function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0; return document.documentElement.scrollTop || document.body.scrollTop || 0;

@ -60,12 +60,12 @@ const movieIndexSelector = createSelector(
} }
); );
const Cell: React.FC<GridChildComponentProps<CellItemData>> = ({ function Cell({
columnIndex, columnIndex,
rowIndex, rowIndex,
style, style,
data, data,
}) => { }: GridChildComponentProps<CellItemData>) {
const { layout, items, sortKey, isSelectMode } = data; const { layout, items, sortKey, isSelectMode } = data;
const { columnCount, padding, posterWidth, posterHeight } = layout; const { columnCount, padding, posterWidth, posterHeight } = layout;
@ -94,7 +94,7 @@ const Cell: React.FC<GridChildComponentProps<CellItemData>> = ({
/> />
</div> </div>
); );
}; }
function getWindowScrollTopPosition() { function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0; return document.documentElement.scrollTop || document.body.scrollTop || 0;

@ -202,9 +202,7 @@ function EditMoviesModalContent(props: EditMoviesModalContentProps) {
includeNoChange={true} includeNoChange={true}
includeNoChangeDisabled={false} includeNoChangeDisabled={false}
selectedValueOptions={{ includeFreeSpace: false }} selectedValueOptions={{ includeFreeSpace: false }}
helpText={ helpText="Moving movies to the same root folder can be used to rename movie folders to match updated title or naming format"
'Moving movies to the same root folder can be used to rename movie folders to match updated title or naming format'
}
onChange={onInputChange} onChange={onInputChange}
/> />
</FormGroup> </FormGroup>

@ -44,11 +44,7 @@ const columnsSelector = createSelector(
(columns) => columns (columns) => columns
); );
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
index,
style,
data,
}) => {
const { items, sortKey, columns, isSelectMode } = data; const { items, sortKey, columns, isSelectMode } = data;
if (index >= items.length) { if (index >= items.length) {
@ -74,7 +70,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
/> />
</div> </div>
); );
}; }
function getWindowScrollTopPosition() { function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0; return document.documentElement.scrollTop || document.body.scrollTop || 0;

@ -1,4 +1,4 @@
import React, { Fragment, useCallback } from 'react'; import React, { useCallback } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
@ -32,19 +32,17 @@ function MovieIndexTableOptions(props: MovieIndexTableOptionsProps) {
); );
return ( return (
<Fragment> <FormGroup>
<FormGroup> <FormLabel>{translate('ShowSearch')}</FormLabel>
<FormLabel>{translate('ShowSearch')}</FormLabel>
<FormInputGroup
<FormInputGroup type={inputTypes.CHECK}
type={inputTypes.CHECK} name="showSearchAction"
name="showSearchAction" value={showSearchAction}
value={showSearchAction} helpText={translate('ShowSearchHelpText')}
helpText={translate('ShowSearchHelpText')} onChange={onTableOptionChangeWrapper}
onChange={onTableOptionChangeWrapper} />
/> </FormGroup>
</FormGroup>
</Fragment>
); );
} }

@ -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 PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import ParseModal from 'Parse/ParseModal'; import ParseModal from 'Parse/ParseModal';
@ -16,7 +16,7 @@ function ParseToolbarButton() {
}, [setIsParseModalOpen]); }, [setIsParseModalOpen]);
return ( return (
<Fragment> <>
<PageToolbarButton <PageToolbarButton
label={translate('TestParsing')} label={translate('TestParsing')}
iconName={icons.PARSE} iconName={icons.PARSE}
@ -24,7 +24,7 @@ function ParseToolbarButton() {
/> />
<ParseModal isOpen={isParseModalOpen} onModalClose={onParseModalClose} /> <ParseModal isOpen={isParseModalOpen} onModalClose={onParseModalClose} />
</Fragment> </>
); );
} }

@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import { DndProvider } from 'react-dnd'; import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend'; import { HTML5Backend } from 'react-dnd-html5-backend';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
@ -17,11 +17,11 @@ function CustomFormatSettingsPage() {
// @ts-ignore // @ts-ignore
showSave={false} showSave={false}
additionalButtons={ additionalButtons={
<Fragment> <>
<PageToolbarSeparator /> <PageToolbarSeparator />
<ParseToolbarButton /> <ParseToolbarButton />
</Fragment> </>
} }
/> />

@ -231,9 +231,9 @@ function ManageDownloadClientsModalContent(
selectAll={true} selectAll={true}
allSelected={allSelected} allSelected={allSelected}
allUnselected={allUnselected} allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey} sortKey={sortKey}
sortDirection={sortDirection} sortDirection={sortDirection}
onSelectAllChange={onSelectAllChange}
onSortPress={onSortPress} onSortPress={onSortPress}
> >
<TableBody> <TableBody>
@ -286,9 +286,9 @@ function ManageDownloadClientsModalContent(
<ManageDownloadClientsEditModal <ManageDownloadClientsEditModal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
downloadClientIds={selectedIds}
onModalClose={onEditModalClose} onModalClose={onEditModalClose}
onSavePress={onSavePress} onSavePress={onSavePress}
downloadClientIds={selectedIds}
/> />
<TagsModal <TagsModal

@ -273,9 +273,9 @@ function ManageImportListsModalContent(
<ManageImportListsEditModal <ManageImportListsEditModal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
importListIds={selectedIds}
onModalClose={onEditModalClose} onModalClose={onEditModalClose}
onSavePress={onSavePress} onSavePress={onSavePress}
importListIds={selectedIds}
/> />
<TagsModal <TagsModal

@ -226,9 +226,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
selectAll={true} selectAll={true}
allSelected={allSelected} allSelected={allSelected}
allUnselected={allUnselected} allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey} sortKey={sortKey}
sortDirection={sortDirection} sortDirection={sortDirection}
onSelectAllChange={onSelectAllChange}
onSortPress={onSortPress} onSortPress={onSortPress}
> >
<TableBody> <TableBody>
@ -281,9 +281,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
<ManageIndexersEditModal <ManageIndexersEditModal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
indexerIds={selectedIds}
onModalClose={onEditModalClose} onModalClose={onEditModalClose}
onSavePress={onSavePress} onSavePress={onSavePress}
indexerIds={selectedIds}
/> />
<TagsModal <TagsModal

Loading…
Cancel
Save