From 77691a963134505deb3520c967db86a26c2fc64f Mon Sep 17 00:00:00 2001 From: nixocio Date: Wed, 20 May 2020 15:52:59 -0400 Subject: [PATCH] Make consistent usage of `useRequest` to delete items Make consistent usage of `useRequest` to delete items. This change is required to avoid warnings such as `Warning: Can't perform a React state update on an unmounted component.` See: https://github.com/ansible/awx/issues/7105 --- .../ScheduleDetail/ScheduleDetail.jsx | 45 ++++++++++--------- .../CredentialDetail/CredentialDetail.jsx | 34 +++++++------- .../OrganizationDetail/OrganizationDetail.jsx | 33 +++++++------- .../Project/ProjectDetail/ProjectDetail.jsx | 35 ++++++--------- .../screens/Team/TeamDetail/TeamDetail.jsx | 33 ++++++-------- .../screens/User/UserDetail/UserDetail.jsx | 35 ++++++--------- 6 files changed, 100 insertions(+), 115 deletions(-) diff --git a/awx/ui_next/src/components/Schedule/ScheduleDetail/ScheduleDetail.jsx b/awx/ui_next/src/components/Schedule/ScheduleDetail/ScheduleDetail.jsx index a63380083a..354a283b09 100644 --- a/awx/ui_next/src/components/Schedule/ScheduleDetail/ScheduleDetail.jsx +++ b/awx/ui_next/src/components/Schedule/ScheduleDetail/ScheduleDetail.jsx @@ -1,5 +1,5 @@ import 'styled-components/macro'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { Link, useHistory, useLocation } from 'react-router-dom'; import { RRule, rrulestr } from 'rrule'; import styled from 'styled-components'; @@ -16,7 +16,7 @@ import { DetailList, Detail, UserDateDetail } from '../../DetailList'; import ScheduleOccurrences from '../ScheduleOccurrences'; import ScheduleToggle from '../ScheduleToggle'; import { formatDateString } from '../../../util/dates'; -import useRequest from '../../../util/useRequest'; +import useRequest, { useDismissableError } from '../../../util/useRequest'; import { SchedulesAPI } from '../../../api'; import DeleteButton from '../../DeleteButton'; import ErrorDetail from '../../ErrorDetail'; @@ -48,27 +48,27 @@ function ScheduleDetail({ schedule, i18n }) { timezone, } = schedule; - const [deletionError, setDeletionError] = useState(null); - const [hasContentLoading, setHasContentLoading] = useState(false); const history = useHistory(); const { pathname } = useLocation(); const pathRoot = pathname.substr(0, pathname.indexOf('schedules')); - const handleDelete = async () => { - setHasContentLoading(true); - try { + const { + request: deleteSchedule, + isLoading: isDeleteLoading, + error: deleteError, + } = useRequest( + useCallback(async () => { await SchedulesAPI.destroy(id); history.push(`${pathRoot}schedules`); - } catch (error) { - setDeletionError(error); - } - setHasContentLoading(false); - }; + }, [id, history, pathRoot]) + ); + + const { error, dismissError } = useDismissableError(deleteError); const { result: [credentials, preview], isLoading, - error, + error: readContentError, request: fetchCredentialsAndPreview, } = useRequest( useCallback(async () => { @@ -102,12 +102,12 @@ function ScheduleDetail({ schedule, i18n }) { (job_tags && job_tags.length > 0) || (skip_tags && skip_tags.length > 0); - if (isLoading || hasContentLoading) { + if (isLoading) { return ; } - if (error) { - return ; + if (readContentError) { + return ; } return ( @@ -237,21 +237,22 @@ function ScheduleDetail({ schedule, i18n }) { {i18n._(t`Delete`)} )} - {deletionError && ( + {error && ( setDeletionError(null)} + onClose={dismissError} > {i18n._(t`Failed to delete schedule.`)} - + )} diff --git a/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx b/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx index 18b01bd0f9..2e8809955f 100644 --- a/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx +++ b/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; @@ -18,6 +18,7 @@ import { import ErrorDetail from '../../../components/ErrorDetail'; import { CredentialsAPI, CredentialTypesAPI } from '../../../api'; import { Credential } from '../../../types'; +import useRequest, { useDismissableError } from '../../../util/useRequest'; function CredentialDetail({ i18n, credential }) { const { @@ -39,7 +40,6 @@ function CredentialDetail({ i18n, credential }) { const [fields, setFields] = useState([]); const [managedByTower, setManagedByTower] = useState([]); const [contentError, setContentError] = useState(null); - const [deletionError, setDeletionError] = useState(null); const [hasContentLoading, setHasContentLoading] = useState(true); const history = useHistory(); @@ -62,17 +62,18 @@ function CredentialDetail({ i18n, credential }) { })(); }, [credential_type]); - const handleDelete = async () => { - setHasContentLoading(true); - - try { + const { + request: deleteCredential, + isLoading, + error: deleteError, + } = useRequest( + useCallback(async () => { await CredentialsAPI.destroy(credentialId); history.push('/credentials'); - } catch (error) { - setDeletionError(error); - } - setHasContentLoading(false); - }; + }, [credentialId, history]) + ); + + const { error, dismissError } = useDismissableError(deleteError); const renderDetail = ({ id, label, type }) => { let detail; @@ -161,21 +162,22 @@ function CredentialDetail({ i18n, credential }) { {i18n._(t`Delete`)} )} - {deletionError && ( + {error && ( setDeletionError(null)} + onClose={dismissError} > {i18n._(t`Failed to delete credential.`)} - + )} diff --git a/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx b/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx index ff6bd42012..522607e6d7 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { Link, useHistory, useRouteMatch } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; @@ -16,6 +16,7 @@ import ContentError from '../../../components/ContentError'; import ContentLoading from '../../../components/ContentLoading'; import DeleteButton from '../../../components/DeleteButton'; import ErrorDetail from '../../../components/ErrorDetail'; +import useRequest, { useDismissableError } from '../../../util/useRequest'; function OrganizationDetail({ i18n, organization }) { const { @@ -31,7 +32,6 @@ function OrganizationDetail({ i18n, organization }) { summary_fields, } = organization; const [contentError, setContentError] = useState(null); - const [deletionError, setDeletionError] = useState(null); const [hasContentLoading, setHasContentLoading] = useState(true); const [instanceGroups, setInstanceGroups] = useState([]); const history = useHistory(); @@ -53,16 +53,18 @@ function OrganizationDetail({ i18n, organization }) { })(); }, [id]); - const handleDelete = async () => { - setHasContentLoading(true); - try { + const { + request: deleteOrganization, + isLoading, + error: deleteError, + } = useRequest( + useCallback(async () => { await OrganizationsAPI.destroy(id); history.push(`/organizations`); - } catch (error) { - setDeletionError(error); - } - setHasContentLoading(false); - }; + }, [id, history]) + ); + + const { error, dismissError } = useDismissableError(deleteError); if (hasContentLoading) { return ; @@ -127,22 +129,23 @@ function OrganizationDetail({ i18n, organization }) { {i18n._(t`Delete`)} )} {/* Update delete modal to show dependencies https://github.com/ansible/awx/issues/5546 */} - {deletionError && ( + {error && ( setDeletionError(null)} + onClose={dismissError} > {i18n._(t`Failed to delete organization.`)} - + )} diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx index a549c7adba..380196d950 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; @@ -8,7 +8,6 @@ import { Config } from '../../../contexts/Config'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; -import ContentLoading from '../../../components/ContentLoading'; import DeleteButton from '../../../components/DeleteButton'; import { DetailList, @@ -19,6 +18,7 @@ import ErrorDetail from '../../../components/ErrorDetail'; import CredentialChip from '../../../components/CredentialChip'; import { ProjectsAPI } from '../../../api'; import { toTitleCase } from '../../../util/strings'; +import useRequest, { useDismissableError } from '../../../util/useRequest'; function ProjectDetail({ project, i18n }) { const { @@ -40,20 +40,16 @@ function ProjectDetail({ project, i18n }) { scm_url, summary_fields, } = project; - const [deletionError, setDeletionError] = useState(null); - const [hasContentLoading, setHasContentLoading] = useState(false); const history = useHistory(); - const handleDelete = async () => { - setHasContentLoading(true); - try { + const { request: deleteProject, isLoading, error: deleteError } = useRequest( + useCallback(async () => { await ProjectsAPI.destroy(id); history.push(`/projects`); - } catch (error) { - setDeletionError(error); - } - setHasContentLoading(false); - }; + }, [id, history]) + ); + + const { error, dismissError } = useDismissableError(deleteError); let optionsList = ''; if ( @@ -78,10 +74,6 @@ function ProjectDetail({ project, i18n }) { ); } - if (hasContentLoading) { - return ; - } - return ( @@ -171,22 +163,23 @@ function ProjectDetail({ project, i18n }) { {i18n._(t`Delete`)} )} {/* Update delete modal to show dependencies https://github.com/ansible/awx/issues/5546 */} - {deletionError && ( + {error && ( setDeletionError(null)} + onClose={dismissError} > {i18n._(t`Failed to delete project.`)} - + )} diff --git a/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx b/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx index f607800fcd..8347c1c735 100644 --- a/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx +++ b/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback } from 'react'; import { Link, useHistory, useParams } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; @@ -6,34 +6,26 @@ import { Button } from '@patternfly/react-core'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; -import ContentLoading from '../../../components/ContentLoading'; import DeleteButton from '../../../components/DeleteButton'; import { DetailList, Detail } from '../../../components/DetailList'; import ErrorDetail from '../../../components/ErrorDetail'; import { formatDateString } from '../../../util/dates'; import { TeamsAPI } from '../../../api'; +import useRequest, { useDismissableError } from '../../../util/useRequest'; function TeamDetail({ team, i18n }) { const { name, description, created, modified, summary_fields } = team; - const [deletionError, setDeletionError] = useState(null); - const [hasContentLoading, setHasContentLoading] = useState(false); const history = useHistory(); const { id } = useParams(); - const handleDelete = async () => { - setHasContentLoading(true); - try { + const { request: deleteTeam, isLoading, error: deleteError } = useRequest( + useCallback(async () => { await TeamsAPI.destroy(id); history.push(`/teams`); - } catch (error) { - setDeletionError(error); - } - setHasContentLoading(false); - }; + }, [id, history]) + ); - if (hasContentLoading) { - return ; - } + const { error, dismissError } = useDismissableError(deleteError); return ( @@ -74,21 +66,22 @@ function TeamDetail({ team, i18n }) { {i18n._(t`Delete`)} )} - {deletionError && ( + {error && ( setDeletionError(null)} + onClose={dismissError} > {i18n._(t`Failed to delete team.`)} - + )} diff --git a/awx/ui_next/src/screens/User/UserDetail/UserDetail.jsx b/awx/ui_next/src/screens/User/UserDetail/UserDetail.jsx index aa2889921a..e4c0a1adfd 100644 --- a/awx/ui_next/src/screens/User/UserDetail/UserDetail.jsx +++ b/awx/ui_next/src/screens/User/UserDetail/UserDetail.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; @@ -6,12 +6,12 @@ import { t } from '@lingui/macro'; import { Button } from '@patternfly/react-core'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; -import ContentLoading from '../../../components/ContentLoading'; import DeleteButton from '../../../components/DeleteButton'; import { DetailList, Detail } from '../../../components/DetailList'; import ErrorDetail from '../../../components/ErrorDetail'; import { formatDateString } from '../../../util/dates'; import { UsersAPI } from '../../../api'; +import useRequest, { useDismissableError } from '../../../util/useRequest'; function UserDetail({ user, i18n }) { const { @@ -26,20 +26,16 @@ function UserDetail({ user, i18n }) { is_system_auditor, summary_fields, } = user; - const [deletionError, setDeletionError] = useState(null); - const [hasContentLoading, setHasContentLoading] = useState(false); const history = useHistory(); - const handleDelete = async () => { - setHasContentLoading(true); - try { + const { request: deleteUser, isLoading, error: deleteError } = useRequest( + useCallback(async () => { await UsersAPI.destroy(id); history.push(`/users`); - } catch (error) { - setDeletionError(error); - } - setHasContentLoading(false); - }; + }, [id, history]) + ); + + const { error, dismissError } = useDismissableError(deleteError); let user_type; if (is_superuser) { @@ -50,10 +46,6 @@ function UserDetail({ user, i18n }) { user_type = i18n._(t`Normal User`); } - if (hasContentLoading) { - return ; - } - return ( @@ -90,21 +82,22 @@ function UserDetail({ user, i18n }) { {i18n._(t`Delete`)} )} - {deletionError && ( + {error && ( setDeletionError(null)} + onClose={dismissError} > {i18n._(t`Failed to delete user.`)} - + )}