1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-25 22:46:31 -04:00

Use react-query for Quality Definitions

This commit is contained in:
Mark McDowall
2025-12-29 23:38:30 -08:00
parent 243a3057ae
commit cf593b1f5d
17 changed files with 320 additions and 370 deletions
@@ -1,31 +1,29 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import TextInput from 'Components/Form/TextInput';
import Quality from 'Quality/Quality';
import { setQualityDefinitionValue } from 'Store/Actions/settingsActions';
import { useManageQualityDefinitions } from './useQualityDefinitions';
import styles from './QualityDefinition.css';
interface QualityDefinitionProps {
id: number;
quality: Quality;
title: string;
updateDefinition: ReturnType<
typeof useManageQualityDefinitions
>['updateDefinition'];
}
function QualityDefinition(props: QualityDefinitionProps) {
const { id, quality, title } = props;
const dispatch = useDispatch();
function QualityDefinition({
id,
quality,
title,
updateDefinition,
}: QualityDefinitionProps) {
const handleTitleChange = useCallback(
({ value }: { value: string }) => {
dispatch(
setQualityDefinitionValue({
id,
name: 'title',
value,
})
);
updateDefinition(id, 'title', value);
},
[id, dispatch]
[id, updateDefinition]
);
return (
@@ -1,42 +1,17 @@
import { isEmpty } from 'lodash';
import { useQueryClient } from '@tanstack/react-query';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import usePrevious from 'Helpers/Hooks/usePrevious';
import {
fetchQualityDefinitions,
saveQualityDefinitions,
} from 'Store/Actions/settingsActions';
import {
OnChildStateChange,
SetChildSave,
} from 'typings/Settings/SettingsState';
import translate from 'Utilities/String/translate';
import QualityDefinition from './QualityDefinition';
import { useManageQualityDefinitions } from './useQualityDefinitions';
import styles from './QualityDefinitions.css';
function createQualityDefinitionsSelector() {
return createSelector(
(state: AppState) => state.settings.qualityDefinitions,
(qualityDefinitions) => {
const items = qualityDefinitions.items.map((item) => {
const pendingChanges = qualityDefinitions.pendingChanges[item.id] || {};
return Object.assign({}, item, pendingChanges);
});
return {
...qualityDefinitions,
items,
hasPendingChanges: !isEmpty(qualityDefinitions.pendingChanges),
};
}
);
}
interface QualityDefinitionsProps {
isResettingQualityDefinitions: boolean;
setChildSave: SetChildSave;
@@ -48,21 +23,27 @@ function QualityDefinitions({
setChildSave,
onChildStateChange,
}: QualityDefinitionsProps) {
const dispatch = useDispatch();
const { items, isFetching, isPopulated, isSaving, error, hasPendingChanges } =
useSelector(createQualityDefinitionsSelector());
const queryClient = useQueryClient();
const {
items,
isFetching,
isFetched,
isSaving,
error,
hasPendingChanges,
updateDefinition,
saveQualityDefinitions,
} = useManageQualityDefinitions();
const wasResettingQualityDefinitions = usePrevious(
isResettingQualityDefinitions
);
useEffect(() => {
dispatch(fetchQualityDefinitions());
setChildSave(() => {
dispatch(saveQualityDefinitions());
saveQualityDefinitions();
});
}, [dispatch, setChildSave]);
}, [saveQualityDefinitions, setChildSave]);
useEffect(() => {
onChildStateChange({
@@ -73,16 +54,20 @@ function QualityDefinitions({
useEffect(() => {
if (wasResettingQualityDefinitions && !isResettingQualityDefinitions) {
dispatch(fetchQualityDefinitions());
queryClient.invalidateQueries({ queryKey: ['/qualitydefinition'] });
}
}, [isResettingQualityDefinitions, wasResettingQualityDefinitions, dispatch]);
}, [
isResettingQualityDefinitions,
wasResettingQualityDefinitions,
queryClient,
]);
return (
<FieldSet legend={translate('QualityDefinitions')}>
<PageSectionContent
errorMessage={translate('QualityDefinitionsLoadError')}
isFetching={isFetching}
isPopulated={isPopulated}
isPopulated={isFetched}
error={error}
>
<div className={styles.header}>
@@ -92,7 +77,13 @@ function QualityDefinitions({
<div className={styles.definitions}>
{items.map((item) => {
return <QualityDefinition key={item.id} {...item} />;
return (
<QualityDefinition
key={item.id}
{...item}
updateDefinition={updateDefinition}
/>
);
})}
</div>
</PageSectionContent>
@@ -0,0 +1,99 @@
import { useQueryClient } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react';
import useApiMutation from 'Helpers/Hooks/useApiMutation';
import useApiQuery from 'Helpers/Hooks/useApiQuery';
import { usePendingItemsStore } from 'Helpers/Hooks/usePendingItemsStore';
import QualityDefinitionModel from 'Quality/QualityDefinitionModel';
import { useSaveSettings } from 'Settings/useSettings';
const PATH = '/qualitydefinition';
const DEFAULT_QUALITY_DEFINITIONS: QualityDefinitionModel[] = [];
export const useQualityDefinitions = () => {
const result = useApiQuery<QualityDefinitionModel[]>({
path: PATH,
});
return {
...result,
data: result.data ?? DEFAULT_QUALITY_DEFINITIONS,
};
};
export const useSaveQualityDefinitions = (onSuccess?: () => void) => {
const queryClient = useQueryClient();
const { mutate, isPending, error } = useApiMutation<
QualityDefinitionModel[],
QualityDefinitionModel[]
>({
path: PATH,
method: 'PUT',
mutationOptions: {
onSuccess: (updatedSettings: QualityDefinitionModel[]) => {
queryClient.setQueryData<QualityDefinitionModel[]>(
[PATH],
updatedSettings
);
onSuccess?.();
},
},
});
return {
save: mutate,
isSaving: isPending,
saveError: error,
};
};
export const useManageQualityDefinitions = () => {
const { data, isFetching, isFetched, error } = useQualityDefinitions();
const {
setPendingItem,
clearPendingItems,
getItemsWithPendingChanges,
getPendingChangesForSave,
hasPendingChanges,
} = usePendingItemsStore<QualityDefinitionModel>();
const { save, isSaving, saveError } = useSaveSettings(
PATH,
clearPendingItems
);
const settings = useMemo(() => {
return {
items: getItemsWithPendingChanges(data),
hasPendingChanges,
};
}, [data, getItemsWithPendingChanges, hasPendingChanges]);
const saveQualityDefinitions = useCallback(() => {
const updatedSettings = getPendingChangesForSave(data);
save(updatedSettings);
}, [data, getPendingChangesForSave, save]);
const updateDefinition = useCallback(
<K extends keyof QualityDefinitionModel>(
id: number,
key: keyof QualityDefinitionModel,
value: QualityDefinitionModel[K]
) => {
const originalItem = data.find((def) => def.id === id);
setPendingItem(id, key, value, originalItem);
},
[data, setPendingItem]
);
return {
...settings,
updateDefinition,
saveQualityDefinitions,
isFetching,
isFetched,
isSaving,
error,
saveError,
};
};