mirror of
https://github.com/Radarr/Radarr
synced 2026-01-25 00:41:50 +01:00
Merge pull request #70 from cheir-mneme/fix/p4-frontend
fix(frontend): React quality improvements - keys, types, memoization
This commit is contained in:
commit
32d072dd8a
17 changed files with 70 additions and 57 deletions
|
|
@ -25,8 +25,7 @@ export type SelectContextAction =
|
|||
export type SelectDispatch = (action: SelectContextAction) => void;
|
||||
|
||||
interface SelectProviderOptions<T extends ModelBase> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
children: any;
|
||||
children: React.ReactNode;
|
||||
items: Array<T>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ function ErrorBoundaryError(props: Readonly<ErrorBoundaryErrorProps>) {
|
|||
{error ? <div>{error.message}</div> : null}
|
||||
|
||||
{detailedError ? (
|
||||
detailedError.map((d, index) => {
|
||||
detailedError.map((d) => {
|
||||
return (
|
||||
<div key={index}>
|
||||
<div key={`${d.fileName}:${d.lineNumber}:${d.columnNumber}`}>
|
||||
{` at ${d.functionName} (${d.fileName}:${d.lineNumber}:${d.columnNumber})`}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ interface AutoSuggestInputProps<T> extends Omit<
|
|||
onChange?: (change: InputChanged<T>) => unknown;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function AutoSuggestInput<T = any>(props: AutoSuggestInputProps<T>) {
|
||||
function AutoSuggestInput<T = unknown>(props: AutoSuggestInputProps<T>) {
|
||||
const {
|
||||
// TODO: forwaredRef should be replaces with React.forwardRef
|
||||
forwardedRef,
|
||||
|
|
|
|||
|
|
@ -21,17 +21,17 @@ function Form({
|
|||
<div id={id}>
|
||||
{validationErrors.length || validationWarnings.length ? (
|
||||
<div className={styles.validationFailures}>
|
||||
{validationErrors.map((error, index) => {
|
||||
{validationErrors.map((error) => {
|
||||
return (
|
||||
<Alert key={index} kind={kinds.DANGER}>
|
||||
<Alert key={error.propertyName} kind={kinds.DANGER}>
|
||||
{error.errorMessage}
|
||||
</Alert>
|
||||
);
|
||||
})}
|
||||
|
||||
{validationWarnings.map((warning, index) => {
|
||||
{validationWarnings.map((warning) => {
|
||||
return (
|
||||
<Alert key={index} kind={kinds.WARNING}>
|
||||
<Alert key={warning.propertyName} kind={kinds.WARNING}>
|
||||
{warning.errorMessage}
|
||||
</Alert>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -269,10 +269,10 @@ function FormInputGroup<T, C extends InputType>(
|
|||
|
||||
{!checkInput && helpTexts ? (
|
||||
<div>
|
||||
{helpTexts.map((text, index) => {
|
||||
{helpTexts.map((text) => {
|
||||
return (
|
||||
<FormInputHelpText
|
||||
key={index}
|
||||
key={text}
|
||||
text={text}
|
||||
isCheckInput={checkInput}
|
||||
/>
|
||||
|
|
@ -287,10 +287,12 @@ function FormInputGroup<T, C extends InputType>(
|
|||
|
||||
{helpLink ? <Link to={helpLink}>{translate('MoreInfo')}</Link> : null}
|
||||
|
||||
{errors.map((error, index) => {
|
||||
{errors.map((error) => {
|
||||
const message =
|
||||
'errorMessage' in error ? error.errorMessage : error.message;
|
||||
return 'errorMessage' in error ? (
|
||||
<FormInputHelpText
|
||||
key={index}
|
||||
key={message}
|
||||
text={error.errorMessage}
|
||||
link={error.infoLink}
|
||||
tooltip={error.detailedDescription}
|
||||
|
|
@ -299,7 +301,7 @@ function FormInputGroup<T, C extends InputType>(
|
|||
/>
|
||||
) : (
|
||||
<FormInputHelpText
|
||||
key={index}
|
||||
key={message}
|
||||
text={error.message}
|
||||
isError={true}
|
||||
isCheckInput={checkInput}
|
||||
|
|
@ -307,10 +309,12 @@ function FormInputGroup<T, C extends InputType>(
|
|||
);
|
||||
})}
|
||||
|
||||
{warnings.map((warning, index) => {
|
||||
{warnings.map((warning) => {
|
||||
const message =
|
||||
'errorMessage' in warning ? warning.errorMessage : warning.message;
|
||||
return 'errorMessage' in warning ? (
|
||||
<FormInputHelpText
|
||||
key={index}
|
||||
key={message}
|
||||
text={warning.errorMessage}
|
||||
link={warning.infoLink}
|
||||
tooltip={warning.detailedDescription}
|
||||
|
|
@ -319,7 +323,7 @@ function FormInputGroup<T, C extends InputType>(
|
|||
/>
|
||||
) : (
|
||||
<FormInputHelpText
|
||||
key={index}
|
||||
key={message}
|
||||
text={warning.message}
|
||||
isWarning={true}
|
||||
isCheckInput={checkInput}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ function KeyValueListInput({
|
|||
>
|
||||
{[...value, { key: '', value: '' }].map((v, index) => (
|
||||
<KeyValueListInputItem
|
||||
key={index}
|
||||
key={`${index}-${v.key}-${v.value}`}
|
||||
index={index}
|
||||
keyValue={v.key}
|
||||
value={v.value}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import { Data, ModifierFn } from 'popper.js';
|
||||
import React, {
|
||||
ElementType,
|
||||
KeyboardEvent,
|
||||
|
|
@ -189,11 +190,10 @@ function EnhancedSelectInput<T extends EnhancedSelectInputValue<V>, V>(
|
|||
return '';
|
||||
}, [value, values, isMultiSelect]);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleComputeMaxHeight = useCallback((data: any) => {
|
||||
const handleComputeMaxHeight: ModifierFn = useCallback((data: Data) => {
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
data.styles.maxHeight = windowHeight - MINIMUM_DISTANCE_FROM_EDGE;
|
||||
data.styles.maxHeight = `${windowHeight - MINIMUM_DISTANCE_FROM_EDGE}px`;
|
||||
|
||||
return data;
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import styles from './LoadingIndicator.css';
|
||||
|
||||
interface LoadingIndicatorProps {
|
||||
|
|
@ -14,20 +14,24 @@ function LoadingIndicator({
|
|||
size = 50,
|
||||
}: Readonly<LoadingIndicatorProps>) {
|
||||
const sizeInPx = `${size}px`;
|
||||
const width = sizeInPx;
|
||||
const height = sizeInPx;
|
||||
|
||||
const containerStyle = useMemo(() => ({ height: sizeInPx }), [sizeInPx]);
|
||||
const rippleContainerStyle = useMemo(
|
||||
() => ({ width: sizeInPx, height: sizeInPx }),
|
||||
[sizeInPx]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className} style={{ height }}>
|
||||
<div className={className} style={containerStyle}>
|
||||
<div
|
||||
className={classNames(styles.rippleContainer, 'followingBalls')}
|
||||
style={{ width, height }}
|
||||
style={rippleContainerStyle}
|
||||
>
|
||||
<div className={rippleClassName} style={{ width, height }} />
|
||||
<div className={rippleClassName} style={rippleContainerStyle} />
|
||||
|
||||
<div className={rippleClassName} style={{ width, height }} />
|
||||
<div className={rippleClassName} style={rippleContainerStyle} />
|
||||
|
||||
<div className={rippleClassName} style={{ width, height }} />
|
||||
<div className={rippleClassName} style={rippleContainerStyle} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { ColorImpairedConsumer } from 'App/ColorImpairedContext';
|
||||
import { Kind } from 'Helpers/Props/kinds';
|
||||
import { Size } from 'Helpers/Props/sizes';
|
||||
|
|
@ -35,6 +35,12 @@ function ProgressBar({
|
|||
const progressText = text || progressPercent;
|
||||
const actualWidth = width ? `${width}px` : '100%';
|
||||
|
||||
const widthStyle = useMemo(() => ({ width: actualWidth }), [actualWidth]);
|
||||
const progressStyle = useMemo(
|
||||
() => ({ width: progressPercent }),
|
||||
[progressPercent]
|
||||
);
|
||||
|
||||
return (
|
||||
<ColorImpairedConsumer>
|
||||
{(enableColorImpairedMode) => {
|
||||
|
|
@ -42,12 +48,12 @@ function ProgressBar({
|
|||
<div
|
||||
className={classNames(containerClassName, styles[size])}
|
||||
title={title}
|
||||
style={{ width: actualWidth }}
|
||||
style={widthStyle}
|
||||
>
|
||||
{showText && width ? (
|
||||
<div
|
||||
className={classNames(styles.backTextContainer, styles[kind])}
|
||||
style={{ width: actualWidth }}
|
||||
style={widthStyle}
|
||||
>
|
||||
<div className={styles.backText}>
|
||||
<div>{progressText}</div>
|
||||
|
|
@ -68,18 +74,15 @@ function ProgressBar({
|
|||
aria-valuenow={Math.floor(progress)}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
style={{ width: progressPercent }}
|
||||
style={progressStyle}
|
||||
/>
|
||||
|
||||
{showText ? (
|
||||
<div
|
||||
className={classNames(styles.frontTextContainer, styles[kind])}
|
||||
style={{ width: progressPercent }}
|
||||
style={progressStyle}
|
||||
>
|
||||
<div
|
||||
className={styles.frontText}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
<div className={styles.frontText} style={widthStyle}>
|
||||
<div>{progressText}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -376,8 +376,8 @@ function InteractiveImportRow(props: Readonly<InteractiveImportRowProps>) {
|
|||
title={translate('ReleaseRejected')}
|
||||
body={
|
||||
<ul>
|
||||
{rejections.map((rejection, index) => {
|
||||
return <li key={index}>{rejection.reason}</li>;
|
||||
{rejections.map((rejection) => {
|
||||
return <li key={rejection.reason}>{rejection.reason}</li>;
|
||||
})}
|
||||
</ul>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -302,8 +302,8 @@ function InteractiveSearchRow(props: Readonly<InteractiveSearchRowProps>) {
|
|||
title={translate('IndexerFlags')}
|
||||
body={
|
||||
<ul>
|
||||
{indexerFlags.map((flag, index) => {
|
||||
return <li key={index}>{flag}</li>;
|
||||
{indexerFlags.map((flag) => {
|
||||
return <li key={flag}>{flag}</li>;
|
||||
})}
|
||||
</ul>
|
||||
}
|
||||
|
|
@ -319,8 +319,8 @@ function InteractiveSearchRow(props: Readonly<InteractiveSearchRowProps>) {
|
|||
title={translate('ReleaseRejected')}
|
||||
body={
|
||||
<ul>
|
||||
{rejections.map((rejection, index) => {
|
||||
return <li key={index}>{rejection}</li>;
|
||||
{rejections.map((rejection) => {
|
||||
return <li key={rejection}>{rejection}</li>;
|
||||
})}
|
||||
</ul>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ function IndexerFlags({ indexerFlags = 0 }: Readonly<IndexerFlagsProps>) {
|
|||
|
||||
return flags.length ? (
|
||||
<ul>
|
||||
{flags.map((flag, index) => {
|
||||
return <li key={index}>{flag.name}</li>;
|
||||
{flags.map((flag) => {
|
||||
return <li key={flag.id}>{flag.name}</li>;
|
||||
})}
|
||||
</ul>
|
||||
) : null;
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ function ExtraFileDetailsPopover(
|
|||
title={translate('Tags')}
|
||||
body={
|
||||
<ul>
|
||||
{details.map(({ name, value }, index) => {
|
||||
{details.map(({ name, value }) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
<li key={name}>
|
||||
{name}: {value}
|
||||
</li>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -41,8 +41,12 @@ function AddRootFolder() {
|
|||
|
||||
<ul>
|
||||
{Array.isArray(saveError.responseJSON) ? (
|
||||
saveError.responseJSON.map((e, index) => {
|
||||
return <li key={index}>{e.errorMessage}</li>;
|
||||
saveError.responseJSON.map((e) => {
|
||||
return (
|
||||
<li key={e.propertyName ?? e.errorMessage}>
|
||||
{e.errorMessage}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<li>{JSON.stringify(saveError.responseJSON)}</li>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export default function AutoTagging({
|
|||
<TagList tags={tags} tagList={tagList} />
|
||||
|
||||
<div>
|
||||
{specifications.map((item, index) => {
|
||||
{specifications.map((item) => {
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ export default function AutoTagging({
|
|||
}
|
||||
|
||||
return (
|
||||
<Label key={index} kind={kind}>
|
||||
<Label key={item.id} kind={kind}>
|
||||
{item.name}
|
||||
</Label>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -54,10 +54,10 @@ export default function AddSpecificationItem({
|
|||
</Button>
|
||||
|
||||
<MenuContent>
|
||||
{presets.map((preset, index) => {
|
||||
{presets.map((preset) => {
|
||||
return (
|
||||
<AddSpecificationPresetMenuItem
|
||||
key={index}
|
||||
key={preset.id}
|
||||
name={preset.name}
|
||||
implementation={implementation}
|
||||
onPress={handleSpecificationSelect}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ function UpdateChanges(props: Readonly<UpdateChangesProps>) {
|
|||
<div>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<ul>
|
||||
{uniqueChanges.map((change, index) => {
|
||||
{uniqueChanges.map((change) => {
|
||||
const checkChange = change.replace(
|
||||
/#\d{4,5}\b/g,
|
||||
(match) =>
|
||||
|
|
@ -30,7 +30,7 @@ function UpdateChanges(props: Readonly<UpdateChangesProps>) {
|
|||
);
|
||||
|
||||
return (
|
||||
<li key={index}>
|
||||
<li key={change}>
|
||||
<InlineMarkdown data={checkChange} />
|
||||
</li>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue