diff --git a/setup/react/app/ApplicationBootstrap.js b/setup/react/app/ApplicationBootstrap.js index 84b392f60c..070123ed93 100644 --- a/setup/react/app/ApplicationBootstrap.js +++ b/setup/react/app/ApplicationBootstrap.js @@ -10,6 +10,7 @@ import { import { useNetworkStatus } from '@network/hooks/queries'; import { useBlockchainApplicationMeta } from '@blockchainApplication/manage/hooks/queries/useBlockchainApplicationMeta'; import { Client } from 'src/utils/api/client'; +import { useReduxStateModifier } from 'src/utils/useReduxStateModifier'; import { useLedgerDeviceListener } from '@libs/hardwareWallet/ledger/ledgerDeviceListener/useLedgerDeviceListener'; export const ApplicationBootstrapContext = createContext({ @@ -80,6 +81,7 @@ const ApplicationBootstrap = ({ children }) => { ]); useLedgerDeviceListener(); + useReduxStateModifier(); return ( ({ network, }); -export const deleteNetworkInApplications = (network) => ({ - type: actionTypes.deleteNetworkInApplications, - network, +export const deleteNetworksInApplications = (networks) => ({ + type: actionTypes.deleteNetworksInApplications, + networks, }); export const updateNetworkNameInApplications = (currentName, newName) => ({ diff --git a/src/modules/blockchainApplication/manage/store/actionTypes.js b/src/modules/blockchainApplication/manage/store/actionTypes.js index d9c2e4181e..93a7b1a10b 100644 --- a/src/modules/blockchainApplication/manage/store/actionTypes.js +++ b/src/modules/blockchainApplication/manage/store/actionTypes.js @@ -4,7 +4,7 @@ const actionTypes = { deleteApplicationByChainId: 'DELETE_APPLICATION_BY_CHAIN_ID', setCurrentApplication: 'SET_CURRENT_APPLICATION', setApplications: 'SET_APPLICATIONS', - deleteNetworkInApplications: 'DELETE_NETWORK_IN_APPLICATIONS', + deleteNetworksInApplications: 'DELETE_NETWORKS_IN_APPLICATIONS', updateNetworkNameInApplications: 'UPDATE_NETWORK_NAME_IN_APPLICATIONS', }; diff --git a/src/modules/blockchainApplication/manage/store/reducer.js b/src/modules/blockchainApplication/manage/store/reducer.js index e93d3b661f..cb6e84ebcf 100644 --- a/src/modules/blockchainApplication/manage/store/reducer.js +++ b/src/modules/blockchainApplication/manage/store/reducer.js @@ -33,7 +33,7 @@ export const pins = (state = initialState.pins, { type, chainId }) => { export const applications = ( state = initialState.applications, - { type, app, apps, chainId, network, currentName, newName } + { type, app, apps, chainId, network, currentName, newName, networks } ) => { switch (type) { case actionTypes.addApplicationByChainId: @@ -49,8 +49,10 @@ export const applications = ( ); } - case actionTypes.deleteNetworkInApplications: { - delete state[network]; + case actionTypes.deleteNetworksInApplications: { + networks.forEach((networkToDelete) => { + delete state[networkToDelete]; + }); return { ...state }; } diff --git a/src/modules/blockchainApplication/manage/store/reducer.test.js b/src/modules/blockchainApplication/manage/store/reducer.test.js index 13e4e7e925..071a4c8bda 100644 --- a/src/modules/blockchainApplication/manage/store/reducer.test.js +++ b/src/modules/blockchainApplication/manage/store/reducer.test.js @@ -68,10 +68,10 @@ describe('BlockchainApplication reducer', () => { expect(changedState).not.toHaveProperty(actionData.chainId); }); - it('should delete network in applications', async () => { + it('should delete networks in applications', async () => { const actionData = { - type: actionTypes.deleteNetworkInApplications, - network: 'devnet', + type: actionTypes.deleteNetworksInApplications, + networks: ['devnet'], }; const changedState = applications( diff --git a/src/modules/network/components/DialogRemoveNetwork/DialogRemoveNetwork.js b/src/modules/network/components/DialogRemoveNetwork/DialogRemoveNetwork.js index 35c87ad15f..f4cd08a0f5 100644 --- a/src/modules/network/components/DialogRemoveNetwork/DialogRemoveNetwork.js +++ b/src/modules/network/components/DialogRemoveNetwork/DialogRemoveNetwork.js @@ -11,7 +11,7 @@ import { OutlineButton, PrimaryButton } from '@theme/buttons'; import useSettings from '@settings/hooks/useSettings'; import { parseSearchParams, removeSearchParamsFromUrl } from 'src/utils/searchParams'; import { immutableDeleteFromArrayById } from 'src/utils/immutableUtils'; -import { deleteNetworkInApplications } from '@blockchainApplication/manage/store/action'; +import { deleteNetworksInApplications } from '@blockchainApplication/manage/store/action'; import { useDispatch } from 'react-redux'; import styles from './DialogRemoveNetwork.css'; @@ -29,7 +29,7 @@ const DialogRemoveNetwork = () => { const onConfirm = () => { const modifiedCustomNetworks = immutableDeleteFromArrayById(customNetworks, 'name', name); setValue(modifiedCustomNetworks); - dispatch(deleteNetworkInApplications(name)); + dispatch(deleteNetworksInApplications([name])); removeSearchParamsFromUrl(history, ['modal'], true); toast.info(t(`Network removed "${name}"`), { position: 'bottom-right' }); }; diff --git a/src/modules/settings/store/reducer.js b/src/modules/settings/store/reducer.js index 8c6e7eb2c5..feddb3a64c 100644 --- a/src/modules/settings/store/reducer.js +++ b/src/modules/settings/store/reducer.js @@ -1,5 +1,4 @@ import { persistReducer } from 'redux-persist'; -import { deepMergeObj } from 'src/utils/helpers'; import { storage } from 'src/redux/store'; import networks from '@network/configuration/networks'; import { DEFAULT_NETWORK } from 'src/const/config'; @@ -33,7 +32,7 @@ export const initialState = { const settings = (state = initialState, action) => { switch (action.type) { case actionTypes.settingsUpdated: - return deepMergeObj(state, action.data); + return { ...state, ...action.data }; case actionTypes.settingsReset: return { ...state, diff --git a/src/utils/useReduxStateModifier.js b/src/utils/useReduxStateModifier.js new file mode 100644 index 0000000000..11318ce3f8 --- /dev/null +++ b/src/utils/useReduxStateModifier.js @@ -0,0 +1,47 @@ +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { selectApplications } from '@blockchainApplication/manage/store/selectors'; +import networks from '@network/configuration/networks'; +import { deleteNetworksInApplications } from '@blockchainApplication/manage/store/action'; +import useSettings from '@settings/hooks/useSettings'; + +const useReduxStateModifier = () => { + const dispatch = useDispatch(); + const { customNetworks } = useSettings('customNetworks'); + const { setValue: setMainChainNetwork, mainChainNetwork } = useSettings('mainChainNetwork'); + const applications = useSelector(selectApplications) || {}; + + const removeDeadApplicationNetworkDomains = () => { + const customNetworksHashmap = customNetworks.reduce((accum, customNetwork) => { + accum[customNetwork.name] = customNetwork; + return accum; + }, {}); + + const allNetworks = { ...networks, ...customNetworksHashmap }; + + const applicationNetworkDomainsToRemove = Object.keys(applications).filter( + (applicationName) => { + const network = allNetworks[applicationName]; + return !network; + } + ); + if (applicationNetworkDomainsToRemove.length > 0) { + dispatch(deleteNetworksInApplications(applicationNetworkDomainsToRemove)); + } + }; + + const removeCircularMainChainNetwork = () => { + const { mainChainNetwork: circularObject, ...rest } = mainChainNetwork; + + if (circularObject) { + setMainChainNetwork(rest); + } + }; + + useEffect(() => { + removeDeadApplicationNetworkDomains(); + removeCircularMainChainNetwork(); + }, []); +}; + +export { useReduxStateModifier }; diff --git a/src/utils/useReduxStateModifier.test.js b/src/utils/useReduxStateModifier.test.js new file mode 100644 index 0000000000..6f31996208 --- /dev/null +++ b/src/utils/useReduxStateModifier.test.js @@ -0,0 +1,55 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { queryWrapper as wrapper } from 'src/utils/test/queryWrapper'; +import useSettings from '@settings/hooks/useSettings'; +import { applicationsMap } from '@tests/fixtures/blockchainApplicationsManage'; + +import networks from '@network/configuration/networks'; +import { deleteNetworksInApplications } from '@blockchainApplication/manage/store/action'; +import { useReduxStateModifier } from './useReduxStateModifier'; + +jest.mock('@settings/hooks/useSettings'); + +jest.useRealTimers(); + +const mockState = { + blockChainApplications: { + applications: { devnet: applicationsMap, toBeRemoved: applicationsMap }, + }, +}; + +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => ({ + useSelector: jest.fn().mockImplementation((fn) => fn(mockState)), + useDispatch: () => mockDispatch, +})); + +const mockSetValueToSettings = jest.fn(); + +useSettings.mockReturnValue({ + customNetworks: [ + { + name: 'custom_network_one', + label: 'custom_network_one', + serviceUrl: 'http://custom-network-service.com', + wsServiceUrl: 'http://custom-network-service.com', + isAvailable: true, + }, + ], + mainChainNetwork: { + ...networks.devnet, + mainChainNetwork: networks.devnet, + }, + setValue: mockSetValueToSettings, +}); + +describe('useReduxStateModifier hook', () => { + it('Should remove circular mainChainNetwork', async () => { + renderHook(() => useReduxStateModifier(), { wrapper }); + expect(mockSetValueToSettings).toHaveBeenCalledWith(networks.devnet); + }); + + it('Should remove dead application network domains', async () => { + renderHook(() => useReduxStateModifier(), { wrapper }); + expect(mockDispatch).toHaveBeenCalledWith(deleteNetworksInApplications(['toBeRemoved'])); + }); +});