From f63312c81135cd0bd53b750d5b7eabd0a59bd784 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 29 Apr 2021 13:53:53 -0400 Subject: [PATCH 1/2] Prevent multi credential state updates from happening after unmount --- .../Lookup/MultiCredentialsLookup.jsx | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx index ecc1a268c4..d64c397c7e 100644 --- a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx +++ b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx @@ -1,5 +1,11 @@ import 'styled-components/macro'; -import React, { Fragment, useState, useCallback, useEffect } from 'react'; +import React, { + Fragment, + useState, + useCallback, + useEffect, + useRef, +} from 'react'; import { withRouter } from 'react-router-dom'; import PropTypes from 'prop-types'; import { withI18n } from '@lingui/react'; @@ -26,6 +32,7 @@ async function loadCredentials(params, selectedCredentialTypeId) { } function MultiCredentialsLookup(props) { + const isMounted = useRef(null); const { value, onChange, onError, history, i18n } = props; const [selectedType, setSelectedType] = useState(null); @@ -37,6 +44,9 @@ function MultiCredentialsLookup(props) { } = useRequest( useCallback(async () => { const types = await CredentialTypesAPI.loadAllTypes(); + if (!isMounted.current) { + return; + } const match = types.find(type => type.kind === 'ssh') || types[0]; setSelectedType(match); return types; @@ -45,7 +55,11 @@ function MultiCredentialsLookup(props) { ); useEffect(() => { + isMounted.current = true; fetchTypes(); + return () => { + isMounted.current = false; + }; }, [fetchTypes]); const { @@ -72,6 +86,10 @@ function MultiCredentialsLookup(props) { CredentialsAPI.readOptions(), ]); + if (!isMounted.current) { + return; + } + results.map(result => { if (result.kind === 'vault' && result.inputs?.vault_id) { result.label = `${result.name} | ${result.inputs.vault_id}`; @@ -101,7 +119,11 @@ function MultiCredentialsLookup(props) { ); useEffect(() => { + isMounted.current = true; fetchCredentials(); + return () => { + isMounted.current = false; + }; }, [fetchCredentials]); useEffect(() => { From 5b716814944ead54185d960466640cbaaf486a86 Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 30 Apr 2021 15:31:36 -0400 Subject: [PATCH 2/2] Fixes test warnings where state updates were being triggered after component unmounts --- .../Lookup/MultiCredentialsLookup.jsx | 31 +++++-------------- .../MultiSelect/useSyncedSelectValue.js | 11 ++++++- .../Inventory/InventoryEdit/InventoryEdit.jsx | 10 +++--- .../InventorySourceDetail.jsx | 9 ++---- .../src/screens/Job/JobOutput/JobOutput.jsx | 5 ++- .../Template/shared/JobTemplateForm.jsx | 9 ++++-- .../screens/Template/shared/LabelSelect.jsx | 14 +++++++-- awx/ui_next/src/util/useIsMounted.js | 12 +++++++ awx/ui_next/src/util/useRequest.js | 12 ++----- 9 files changed, 59 insertions(+), 54 deletions(-) create mode 100644 awx/ui_next/src/util/useIsMounted.js diff --git a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx index d64c397c7e..d2b4e3fb1e 100644 --- a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx +++ b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx @@ -1,11 +1,5 @@ import 'styled-components/macro'; -import React, { - Fragment, - useState, - useCallback, - useEffect, - useRef, -} from 'react'; +import React, { Fragment, useState, useCallback, useEffect } from 'react'; import { withRouter } from 'react-router-dom'; import PropTypes from 'prop-types'; import { withI18n } from '@lingui/react'; @@ -18,6 +12,7 @@ import OptionsList from '../OptionsList'; import useRequest from '../../util/useRequest'; import { getQSConfig, parseQueryString } from '../../util/qs'; import Lookup from './Lookup'; +import useIsMounted from '../../util/useIsMounted'; const QS_CONFIG = getQSConfig('credentials', { page: 1, @@ -32,9 +27,9 @@ async function loadCredentials(params, selectedCredentialTypeId) { } function MultiCredentialsLookup(props) { - const isMounted = useRef(null); const { value, onChange, onError, history, i18n } = props; const [selectedType, setSelectedType] = useState(null); + const isMounted = useIsMounted(); const { result: credentialTypes, @@ -44,22 +39,18 @@ function MultiCredentialsLookup(props) { } = useRequest( useCallback(async () => { const types = await CredentialTypesAPI.loadAllTypes(); - if (!isMounted.current) { - return; - } const match = types.find(type => type.kind === 'ssh') || types[0]; - setSelectedType(match); + if (isMounted.current) { + setSelectedType(match); + } return types; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, []), [] ); useEffect(() => { - isMounted.current = true; fetchTypes(); - return () => { - isMounted.current = false; - }; }, [fetchTypes]); const { @@ -86,10 +77,6 @@ function MultiCredentialsLookup(props) { CredentialsAPI.readOptions(), ]); - if (!isMounted.current) { - return; - } - results.map(result => { if (result.kind === 'vault' && result.inputs?.vault_id) { result.label = `${result.name} | ${result.inputs.vault_id}`; @@ -119,11 +106,7 @@ function MultiCredentialsLookup(props) { ); useEffect(() => { - isMounted.current = true; fetchCredentials(); - return () => { - isMounted.current = false; - }; }, [fetchCredentials]); useEffect(() => { diff --git a/awx/ui_next/src/components/MultiSelect/useSyncedSelectValue.js b/awx/ui_next/src/components/MultiSelect/useSyncedSelectValue.js index 1a62a09ec2..1b4c46195d 100644 --- a/awx/ui_next/src/components/MultiSelect/useSyncedSelectValue.js +++ b/awx/ui_next/src/components/MultiSelect/useSyncedSelectValue.js @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import useIsMounted from '../../util/useIsMounted'; /* Hook for using PatternFly's