diff --git a/package.json b/package.json index 0deb2ed2ad6..46cf47375f4 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@react-navigation/material-top-tabs": "^5.3.19", "@react-navigation/native": "^6.1.12", "@react-navigation/native-stack": "^6.9.20", - "@reduxjs/toolkit": "^1.9.7", + "@reduxjs/toolkit": "^2.2.1", "@segment/analytics-react-native": "^2.18.0", "@segment/analytics-react-native-plugin-adjust": "^0.7.0", "@segment/analytics-react-native-plugin-clevertap": "^1.1.0", @@ -190,7 +190,7 @@ "react-native-video": "^6.0.0-beta.3", "react-native-webview": "^13.8.1", "react-redux": "^7.2.9", - "redux": "^4.2.1", + "redux": "^5.0.1", "redux-persist": "^6.0.0", "redux-persist-fs-storage": "^1.3.0", "redux-saga": "^1.3.0", @@ -234,7 +234,7 @@ "@types/react-native-video": "^5.0.15", "@types/react-redux": "^7.1.7", "@types/react-test-renderer": "^18.0.0", - "@types/redux-mock-store": "^1.0.0", + "@types/redux-mock-store": "^1.0.6", "@types/seedrandom": "^3.0.5", "@types/shelljs": "^0.8.11", "@types/utf8": "^2.1.6", @@ -341,7 +341,8 @@ "flat": "^5.0.2", "crypto-js": "^4.2.0", "browserify-sign": "^4.2.2", - "follow-redirects": "^1.15.5" + "follow-redirects": "^1.15.5", + "redux": "^5.0.0" }, "detox": { "testRunner": { diff --git a/src/account/reducer.test.ts b/src/account/reducer.test.ts index 042737788fb..45affd681d7 100644 --- a/src/account/reducer.test.ts +++ b/src/account/reducer.test.ts @@ -1,5 +1,7 @@ -import { reducer, initialState } from 'src/account/reducer' import { Actions, ChooseCreateAccountAction } from 'src/account/actions' +import { reducer } from 'src/account/reducer' + +const initialState = reducer(undefined, { type: 'INIT' } as any) describe('account reducer', () => { describe('CHOOSE_CREATE_ACCOUNT action', () => { diff --git a/src/account/reducer.ts b/src/account/reducer.ts index 01fc8f6db83..453cca12953 100644 --- a/src/account/reducer.ts +++ b/src/account/reducer.ts @@ -7,7 +7,7 @@ import { getRehydratePayload, REHYDRATE, RehydrateAction } from 'src/redux/persi import Logger from 'src/utils/Logger' import { Actions as Web3Actions, ActionTypes as Web3ActionTypes } from 'src/web3/actions' -export interface State { +interface State { name: string | null e164PhoneNumber: string | null pictureUri: string | null @@ -73,7 +73,7 @@ export enum RecoveryPhraseInOnboardingStatus { Completed = 'Completed', // users who have clicked "I've saved it" } -export const initialState: State = { +const initialState: State = { name: null, e164PhoneNumber: null, pictureUri: null, diff --git a/src/alert/AlertBanner.test.tsx b/src/alert/AlertBanner.test.tsx index a3fcf64180c..f86dc32552a 100644 --- a/src/alert/AlertBanner.test.tsx +++ b/src/alert/AlertBanner.test.tsx @@ -1,7 +1,6 @@ import { fireEvent, render, within } from '@testing-library/react-native' import * as React from 'react' import { Provider } from 'react-redux' -import { AnyAction } from 'redux' import { AlertTypes } from 'src/alert/actions' import AlertBanner from 'src/alert/AlertBanner' import { ErrorDisplayType } from 'src/alert/reducer' @@ -83,7 +82,7 @@ describe('AlertBanner', () => { displayMethod: ErrorDisplayType.BANNER, message: 'My message', dismissAfter: 0, - action: { type: 'MY_ACTION' } as AnyAction, + action: { type: 'MY_ACTION' }, }, }) const { getByTestId } = render( @@ -105,7 +104,7 @@ describe('AlertBanner', () => { displayMethod: ErrorDisplayType.BANNER, message: 'My precious toast', buttonMessage: 'Some button label', - action: { type: 'MY_ACTION' } as AnyAction, + action: { type: 'MY_ACTION' }, }, }) const { getByText, queryByTestId } = render( diff --git a/src/alert/reducer.ts b/src/alert/reducer.ts index d275878a299..663e5313be6 100644 --- a/src/alert/reducer.ts +++ b/src/alert/reducer.ts @@ -1,4 +1,4 @@ -import { AnyAction } from 'redux' +import { Action } from '@reduxjs/toolkit' import { Actions, ActionTypes, AlertTypes } from 'src/alert/actions' import { ErrorMessages } from 'src/app/ErrorMessages' import { ActionTypes as ExchangeActionTypes } from 'src/exchange/actions' @@ -15,12 +15,12 @@ export interface Alert { message: string dismissAfter?: number | null buttonMessage?: string | null - action?: AnyAction | null + action?: Action | null title?: string | null underlyingError?: ErrorMessages | null } -export type State = Alert | null +type State = Alert | null const initialState = null diff --git a/src/app/reducers.ts b/src/app/reducers.ts index d37ef9bde9e..bfde29e3746 100644 --- a/src/app/reducers.ts +++ b/src/app/reducers.ts @@ -9,7 +9,7 @@ import { getRehydratePayload, REHYDRATE, RehydrateAction } from 'src/redux/persi const PERSISTED_DEEP_LINKS = ['https://valoraapp.com/share', 'celo://wallet/jumpstart'] -export interface State { +interface State { loggedIn: boolean numberVerified: boolean // decentrally verified phoneNumberVerified: boolean // centrally verified diff --git a/src/dapps/slice.ts b/src/dapps/slice.ts index 1ec76ccb201..b8cab615272 100644 --- a/src/dapps/slice.ts +++ b/src/dapps/slice.ts @@ -4,7 +4,7 @@ import { ActiveDapp, Dapp, DappCategory } from 'src/dapps/types' import { REMOTE_CONFIG_VALUES_DEFAULTS } from 'src/firebase/remoteConfigValuesDefaults' import { getRehydratePayload, REHYDRATE, RehydrateAction } from 'src/redux/persist-helper' -export interface State { +interface State { dappsWebViewEnabled: boolean activeDapp: ActiveDapp | null maxNumRecentDapps: number diff --git a/src/escrow/reducer.ts b/src/escrow/reducer.ts index 77bdd33145f..516d238b7f4 100644 --- a/src/escrow/reducer.ts +++ b/src/escrow/reducer.ts @@ -2,17 +2,20 @@ import { createSelector } from 'reselect' import { Actions, ActionTypes, EscrowedPayment } from 'src/escrow/actions' import { RootState } from 'src/redux/reducers' -export interface State { +interface State { isReclaiming: boolean sentEscrowedPayments: EscrowedPayment[] } -export const initialState = { +const initialState = { isReclaiming: false, sentEscrowedPayments: [], } -export const escrowReducer = (state: State | undefined = initialState, action: ActionTypes) => { +export const escrowReducer = ( + state: State | undefined = initialState, + action: ActionTypes +): State => { switch (action.type) { case Actions.STORE_SENT_PAYMENTS: return { diff --git a/src/exchange/reducer.ts b/src/exchange/reducer.ts index 8e994e3e022..1bdd03b2624 100644 --- a/src/exchange/reducer.ts +++ b/src/exchange/reducer.ts @@ -14,7 +14,7 @@ export interface ExchangeRate { export type ExchangeRates = Record> -export interface State { +interface State { history: { // TODO this should be remove once we have aggregation on // blockchain api side diff --git a/src/fees/reducer.ts b/src/fees/reducer.ts index ef5b1415990..96cf4752dcb 100644 --- a/src/fees/reducer.ts +++ b/src/fees/reducer.ts @@ -23,11 +23,11 @@ export interface FeeEstimates { } } -export interface State { +interface State { estimates: FeeEstimates } -export const initialState: State = { +const initialState: State = { estimates: {}, } diff --git a/src/fiatExchanges/quotes/FiatConnectQuote.ts b/src/fiatExchanges/quotes/FiatConnectQuote.ts index 1b31316866f..0c75ddf859a 100644 --- a/src/fiatExchanges/quotes/FiatConnectQuote.ts +++ b/src/fiatExchanges/quotes/FiatConnectQuote.ts @@ -7,7 +7,7 @@ import { QuoteResponseKycSchema, } from '@fiatconnect/fiatconnect-types' import BigNumber from 'bignumber.js' -import { Dispatch } from 'redux' +import { Dispatch } from '@reduxjs/toolkit' import { FiatConnectProviderInfo, FiatConnectQuoteSuccess } from 'src/fiatconnect' import { selectFiatConnectQuote } from 'src/fiatconnect/slice' import { diff --git a/src/fiatExchanges/quotes/NormalizedQuote.ts b/src/fiatExchanges/quotes/NormalizedQuote.ts index 49b7ed08f54..45872d02ce3 100644 --- a/src/fiatExchanges/quotes/NormalizedQuote.ts +++ b/src/fiatExchanges/quotes/NormalizedQuote.ts @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js' -import { Dispatch } from 'redux' +import { Dispatch } from '@reduxjs/toolkit' import { FiatExchangeEvents } from 'src/analytics/Events' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' import { SettlementEstimation } from 'src/fiatExchanges/quotes/constants' diff --git a/src/fiatExchanges/reducer.ts b/src/fiatExchanges/reducer.ts index 13c475d083c..b70814c1a3a 100644 --- a/src/fiatExchanges/reducer.ts +++ b/src/fiatExchanges/reducer.ts @@ -20,17 +20,20 @@ interface TxHashToDisplayInfo { [txHash: string]: ProviderFeedInfo | undefined } -export interface State { +interface State { txHashToProvider: TxHashToDisplayInfo providerLogos: ProviderLogos } -export const initialState = { +const initialState = { txHashToProvider: {}, providerLogos: {}, } -export const reducer = (state: State = initialState, action: ActionTypes | RehydrateAction) => { +export const reducer = ( + state: State = initialState, + action: ActionTypes | RehydrateAction +): State => { switch (action.type) { case REHYDRATE: { return { diff --git a/src/home/reducers.test.ts b/src/home/reducers.test.ts index 7b7b566a63e..b23873b9f8d 100644 --- a/src/home/reducers.test.ts +++ b/src/home/reducers.test.ts @@ -4,7 +4,12 @@ import { cleverTapInboxMessagesReceived, nftCelebrationDisplayed, } from 'src/home/actions' -import { DEFAULT_PRIORITY, initialState, homeReducer as reducer } from 'src/home/reducers' +import { + DEFAULT_PRIORITY, + Notification, + initialState, + homeReducer as reducer, +} from 'src/home/reducers' import { NetworkId } from 'src/transactions/types' import { mockCleverTapInboxMessage, mockContractAddress } from 'test/values' @@ -90,7 +95,7 @@ describe('home reducer', () => { notifications: { ...updatedState.notifications, notification1: { - ...updatedState.notifications.notification1, + ...(updatedState.notifications.notification1 as Notification), dismissed: true, }, }, @@ -172,9 +177,18 @@ describe('home reducer', () => { }) it('should mark nftCelebration as displayed', () => { - const updatedState = reducer(undefined, nftCelebrationDisplayed()) + const initialState = reducer( + undefined, + celebratedNftFound({ + networkId: NetworkId['celo-alfajores'], + contractAddress: mockContractAddress, + }) + ) + const updatedState = reducer(initialState, nftCelebrationDisplayed()) expect(updatedState.nftCelebration).toEqual({ + networkId: NetworkId['celo-alfajores'], + contractAddress: mockContractAddress, displayed: true, }) }) diff --git a/src/home/reducers.ts b/src/home/reducers.ts index c8ea6821359..dcec306d4a9 100644 --- a/src/home/reducers.ts +++ b/src/home/reducers.ts @@ -30,7 +30,7 @@ export interface IdToNotification { [id: string]: Notification | undefined } -export interface State { +interface State { loading: boolean notifications: IdToNotification cleverTapInboxMessages: CleverTapInboxMessage[] @@ -50,7 +50,10 @@ export const initialState = { nftCelebration: null, } -export const homeReducer = (state: State = initialState, action: ActionTypes | RehydrateAction) => { +export const homeReducer = ( + state: State = initialState, + action: ActionTypes | RehydrateAction +): State => { switch (action.type) { case REHYDRATE: { // Ignore some persisted properties @@ -127,6 +130,9 @@ export const homeReducer = (state: State = initialState, action: ActionTypes | R }, } case Actions.NFT_CELEBRATION_DISPLAYED: + if (!state.nftCelebration) { + return state + } return { ...state, nftCelebration: { diff --git a/src/i18n/slice.ts b/src/i18n/slice.ts index 9473434f5dd..1294c88210c 100644 --- a/src/i18n/slice.ts +++ b/src/i18n/slice.ts @@ -3,7 +3,7 @@ import { Actions as AppActions, UpdateConfigValuesAction } from 'src/app/actions import { REMOTE_CONFIG_VALUES_DEFAULTS } from 'src/firebase/remoteConfigValuesDefaults' import { getRehydratePayload, REHYDRATE, RehydrateAction } from 'src/redux/persist-helper' -export interface State { +interface State { language: string | null allowOtaTranslations: boolean otaTranslationsLastUpdate: number diff --git a/src/identity/reducer.ts b/src/identity/reducer.ts index 3238f46742e..dc61d8cc9ba 100644 --- a/src/identity/reducer.ts +++ b/src/identity/reducer.ts @@ -70,7 +70,7 @@ export interface AddressToVerificationStatus { [address: string]: boolean | undefined } -export interface State { +interface State { hasSeenVerificationNux: boolean addressToE164Number: AddressToE164NumberType // Note: Do not access values in this directly, use the `getAddressFromPhoneNumber` helper in contactMapping diff --git a/src/import/reducer.ts b/src/import/reducer.ts index bbde13847e1..9695be2e4af 100644 --- a/src/import/reducer.ts +++ b/src/import/reducer.ts @@ -1,6 +1,6 @@ import { Actions, ActionTypes } from 'src/import/actions' -export interface State { +interface State { isImportingWallet: boolean } @@ -8,7 +8,7 @@ const initialState = { isImportingWallet: false, } -export const reducer = (state: State | undefined = initialState, action: ActionTypes) => { +export const reducer = (state: State | undefined = initialState, action: ActionTypes): State => { switch (action.type) { case Actions.IMPORT_BACKUP_PHRASE: return { diff --git a/src/jumpstart/slice.ts b/src/jumpstart/slice.ts index fc4c9f2fd27..e830b3572a2 100644 --- a/src/jumpstart/slice.ts +++ b/src/jumpstart/slice.ts @@ -1,6 +1,6 @@ import { createSlice } from '@reduxjs/toolkit' -export interface State { +interface State { claimStatus: 'idle' | 'loading' | 'error' } diff --git a/src/keylessBackup/slice.ts b/src/keylessBackup/slice.ts index 272759f89e2..b18dd0bfd18 100644 --- a/src/keylessBackup/slice.ts +++ b/src/keylessBackup/slice.ts @@ -5,7 +5,7 @@ import { KeylessBackupStatus, } from 'src/keylessBackup/types' -export interface State { +interface State { googleIdToken: string | null valoraKeyshare: string | null torusKeyshare: string | null @@ -14,7 +14,7 @@ export interface State { showDeleteBackupError: boolean } -export const initialState: State = { +const initialState: State = { googleIdToken: null, valoraKeyshare: null, torusKeyshare: null, diff --git a/src/localCurrency/reducer.ts b/src/localCurrency/reducer.ts index 87dd8948f81..b7eeea14735 100644 --- a/src/localCurrency/reducer.ts +++ b/src/localCurrency/reducer.ts @@ -2,7 +2,7 @@ import { Actions, ActionTypes } from 'src/localCurrency/actions' import { LocalCurrencyCode } from 'src/localCurrency/consts' import { getRehydratePayload, REHYDRATE, RehydrateAction } from 'src/redux/persist-helper' -export interface State { +interface State { isLoading: boolean error?: boolean preferredCurrencyCode?: LocalCurrencyCode diff --git a/src/networkInfo/reducer.ts b/src/networkInfo/reducer.ts index 64fcfd9bb58..3f8d0bde924 100644 --- a/src/networkInfo/reducer.ts +++ b/src/networkInfo/reducer.ts @@ -1,8 +1,8 @@ import { REHYDRATE, RehydrateAction } from 'redux-persist' -import { Actions, ActionTypes } from 'src/networkInfo/actions' +import { ActionTypes, Actions } from 'src/networkInfo/actions' import { UserLocationData } from 'src/networkInfo/saga' -export interface State { +interface State { connected: boolean // True if the phone thinks it has a data connection (cellular/Wi-Fi), false otherwise. rehydrated: boolean userLocationData: UserLocationData diff --git a/src/nfts/slice.ts b/src/nfts/slice.ts index 02906f8f5ee..3fe322f8a52 100644 --- a/src/nfts/slice.ts +++ b/src/nfts/slice.ts @@ -9,7 +9,7 @@ export interface FetchNftsFailedAction { error: string } -export type State = { +type State = { nftsLoading: boolean nftsError: string | null nfts: NftWithNetworkId[] diff --git a/src/positions/slice.ts b/src/positions/slice.ts index 3b628d6afbb..1f1e001c98b 100644 --- a/src/positions/slice.ts +++ b/src/positions/slice.ts @@ -18,7 +18,7 @@ export type TriggeredShortcuts = Record< } > -export interface State { +interface State { positions: Position[] status: Status shortcuts: Shortcut[] diff --git a/src/priceHistory/slice.test.ts b/src/priceHistory/slice.test.ts index 529c1990fd5..51b9aac9248 100644 --- a/src/priceHistory/slice.test.ts +++ b/src/priceHistory/slice.test.ts @@ -6,18 +6,18 @@ import reducer, { import { mockCusdTokenId } from 'test/values' it('should handle fetchPriceHistoryStart', () => { - const initialState = {} + const initialState: State = {} const action = fetchPriceHistoryStart({ tokenId: mockCusdTokenId, startTimestamp: 0, endTimestamp: 1000, }) - const resultState = reducer(initialState, action) as State + const resultState = reducer(initialState, action) expect(resultState[mockCusdTokenId]).toEqual({ status: 'loading' }) }) it('should handle fetchPriceHistoryFailure', () => { - const initialState = { + const initialState: State = { [mockCusdTokenId]: { status: 'success', prices: [ @@ -36,7 +36,7 @@ it('should handle fetchPriceHistoryFailure', () => { tokenId: mockCusdTokenId, }) - const resultState = reducer(initialState, action) as State + const resultState = reducer(initialState, action) // Should retain existing prices on failure expect(resultState[mockCusdTokenId]).toEqual({ prices: [ @@ -54,7 +54,7 @@ it('should handle fetchPriceHistoryFailure', () => { }) it('should handle fetchPriceHistorySuccess', () => { - const initialState = { + const initialState: State = { [mockCusdTokenId]: { status: 'loading', prices: [], @@ -77,7 +77,7 @@ it('should handle fetchPriceHistorySuccess', () => { }, } - const resultState = reducer(initialState, action) as State + const resultState = reducer(initialState, action) expect(resultState[mockCusdTokenId]).toEqual({ prices: [ { diff --git a/src/priceHistory/slice.ts b/src/priceHistory/slice.ts index 5c7df4fbf34..32d0c0e2be2 100644 --- a/src/priceHistory/slice.ts +++ b/src/priceHistory/slice.ts @@ -17,7 +17,7 @@ export interface State { const slice = createSlice({ name: 'priceHistory', - initialState: {}, + initialState: {} as State, reducers: { fetchPriceHistoryStart: ( state: Draft, diff --git a/src/recipients/reducer.ts b/src/recipients/reducer.ts index 8908cec433e..271609314c3 100644 --- a/src/recipients/reducer.ts +++ b/src/recipients/reducer.ts @@ -1,10 +1,10 @@ import { createAction, createReducer, createSelector } from '@reduxjs/toolkit' import { addressToDisplayNameSelector, addressToE164NumberSelector } from 'src/identity/selectors' import { AddressToRecipient, NumberToRecipient } from 'src/recipients/recipient' -import { getRehydratePayload, REHYDRATE, RehydrateAction } from 'src/redux/persist-helper' +import { REHYDRATE, RehydrateAction, getRehydratePayload } from 'src/redux/persist-helper' import { RootState } from 'src/redux/reducers' -export interface State { +interface State { // phoneRecipientCache contains the processed contact data imported from the // phone for a single app session. // Think of contacts as raw data and recipients as filtered data diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index 7446f34424c..56718bfa592 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -1,34 +1,34 @@ -import { Action, combineReducers } from 'redux' +import { Action, combineReducers } from '@reduxjs/toolkit' import { PersistState } from 'redux-persist' -import { Actions } from 'src/account/actions' -import { State as AccountState, reducer as account } from 'src/account/reducer' -import { State as AlertState, reducer as alert } from 'src/alert/reducer' -import { State as AppState, appReducer as app } from 'src/app/reducers' -import superchargeReducer, { State as SuperchargeState } from 'src/consumerIncentives/slice' -import dappsReducer, { State as DappsState } from 'src/dapps/slice' -import { State as EscrowState, escrowReducer as escrow } from 'src/escrow/reducer' -import { State as ExchangeState, reducer as exchange } from 'src/exchange/reducer' -import { State as FeesState, reducer as fees } from 'src/fees/reducer' -import { State as FiatExchangesState, reducer as fiatExchanges } from 'src/fiatExchanges/reducer' -import fiatConnectReducer, { State as FiatConnectState } from 'src/fiatconnect/slice' -import { State as HomeState, homeReducer as home } from 'src/home/reducers' -import i18nReducer, { State as I18nState } from 'src/i18n/slice' -import { State as IdentityState, reducer as identity } from 'src/identity/reducer' -import { State as ImportState, reducer as imports } from 'src/import/reducer' -import jumpstartReducer, { State as JumpstartState } from 'src/jumpstart/slice' -import keylessBackupReducer, { State as KeylessBackupState } from 'src/keylessBackup/slice' -import { State as LocalCurrencyState, reducer as localCurrency } from 'src/localCurrency/reducer' -import { State as NetworkInfoState, reducer as networkInfo } from 'src/networkInfo/reducer' -import nftsReducer, { State as NFTsState } from 'src/nfts/slice' -import positionsReducer, { State as PositionsState } from 'src/positions/slice' -import priceHistoryReducer, { State as priceHistoryState } from 'src/priceHistory/slice' -import { State as RecipientsState, recipientsReducer as recipients } from 'src/recipients/reducer' -import { State as SendState, sendReducer as send } from 'src/send/reducers' -import swapReducer, { State as SwapState } from 'src/swap/slice' -import tokenReducer, { State as TokensState } from 'src/tokens/slice' -import { State as TransactionsState, reducer as transactions } from 'src/transactions/reducer' -import { State as WalletConnectState, reducer as walletConnect } from 'src/walletConnect/reducer' -import { State as Web3State, reducer as web3 } from 'src/web3/reducer' +import { Actions, ClearStoredAccountAction } from 'src/account/actions' +import { reducer as account } from 'src/account/reducer' +import { reducer as alert } from 'src/alert/reducer' +import { appReducer as app } from 'src/app/reducers' +import superchargeReducer from 'src/consumerIncentives/slice' +import dappsReducer from 'src/dapps/slice' +import { escrowReducer as escrow } from 'src/escrow/reducer' +import { reducer as exchange } from 'src/exchange/reducer' +import { reducer as fees } from 'src/fees/reducer' +import { reducer as fiatExchanges } from 'src/fiatExchanges/reducer' +import fiatConnectReducer from 'src/fiatconnect/slice' +import { homeReducer as home } from 'src/home/reducers' +import i18nReducer from 'src/i18n/slice' +import { reducer as identity } from 'src/identity/reducer' +import { reducer as imports } from 'src/import/reducer' +import jumpstartReducer from 'src/jumpstart/slice' +import keylessBackupReducer from 'src/keylessBackup/slice' +import { reducer as localCurrency } from 'src/localCurrency/reducer' +import { reducer as networkInfo } from 'src/networkInfo/reducer' +import nftsReducer from 'src/nfts/slice' +import positionsReducer from 'src/positions/slice' +import priceHistoryReducer from 'src/priceHistory/slice' +import { recipientsReducer as recipients } from 'src/recipients/reducer' +import { sendReducer as send } from 'src/send/reducers' +import swapReducer from 'src/swap/slice' +import tokenReducer from 'src/tokens/slice' +import { reducer as transactions } from 'src/transactions/reducer' +import { reducer as walletConnect } from 'src/walletConnect/reducer' +import { reducer as web3 } from 'src/web3/reducer' const appReducer = combineReducers({ app, @@ -59,7 +59,7 @@ const appReducer = combineReducers({ nfts: nftsReducer, priceHistory: priceHistoryReducer, jumpstart: jumpstartReducer, -}) as (state: RootState | undefined, action: Action) => RootState +}) const rootReducer = (state: RootState | undefined, action: Action): RootState => { if (action.type === Actions.CLEAR_STORED_ACCOUNT && state) { @@ -72,58 +72,12 @@ const rootReducer = (state: RootState | undefined, action: Action): RootState => localCurrency: state.localCurrency, // We keep phone number mappings since there's a cost to fetch them and they are // likely to be the same on the same device. - identity: identity(state.identity, action), - } + identity: identity(state.identity, action as ClearStoredAccountAction), + } as RootState } - return appReducer(state, action) + return appReducer(state, action) as RootState } export default rootReducer -export interface RootState { - _persist: PersistState - app: AppState - i18n: I18nState - networkInfo: NetworkInfoState - alert: AlertState - send: SendState - home: HomeState - exchange: ExchangeState - transactions: TransactionsState - web3: Web3State - identity: IdentityState - account: AccountState - escrow: EscrowState - fees: FeesState - recipients: RecipientsState - localCurrency: LocalCurrencyState - imports: ImportState - fiatExchanges: FiatExchangesState - walletConnect: WalletConnectState - tokens: TokensState - supercharge: SuperchargeState - dapps: DappsState - fiatConnect: FiatConnectState - swap: SwapState - positions: PositionsState - keylessBackup: KeylessBackupState - nfts: NFTsState - priceHistory: priceHistoryState - jumpstart: JumpstartState -} - -export interface PersistedRootState { - _persist: PersistState - app: AppState - i18n: I18nState - send: SendState - home: HomeState - transactions: TransactionsState - web3: Web3State - identity: IdentityState - account: AccountState - escrow: EscrowState - localCurrency: LocalCurrencyState - recipients: RecipientsState - fiatExchanges: FiatExchangesState -} +export type RootState = ReturnType & { _persist: PersistState } diff --git a/src/redux/sagas.ts b/src/redux/sagas.ts index 67da4c4736a..15b8a8ea100 100644 --- a/src/redux/sagas.ts +++ b/src/redux/sagas.ts @@ -1,5 +1,5 @@ import { sleep } from '@celo/utils/lib/async' -import { AnyAction } from 'redux' +import { UnknownAction } from '@reduxjs/toolkit' // Import the actions included in the logger blocklist below. import { REHYDRATE } from 'redux-persist' import { Actions as AccountActions } from 'src/account/actions' @@ -68,7 +68,7 @@ function* loggerSaga() { return } - yield* takeEvery('*', (action: AnyAction) => { + yield* takeEvery('*', (action: UnknownAction) => { if ( action?.type && (action.type.includes('IDENTITY/') || loggerBlocklist.includes(action.type)) diff --git a/src/redux/store.ts b/src/redux/store.ts index c47f0792651..800a8a5d3c8 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -1,7 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage' -import { configureStore } from '@reduxjs/toolkit' -import { setupListeners } from '@reduxjs/toolkit/dist/query' -import { Middleware } from 'redux' +import { configureStore, Middleware } from '@reduxjs/toolkit' import { getStoredState, PersistConfig, persistReducer, persistStore } from 'redux-persist' import FSStorage from 'redux-persist-fs-storage' import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2' @@ -92,7 +90,7 @@ export const _persistConfig = persistConfig // eslint-disable-next-line no-var declare var window: any -export const setupStore = (initialState = {}, config = persistConfig) => { +export const setupStore = (initialState?: RootState, config = persistConfig) => { const sagaMiddleware = createSagaMiddleware({ onError: (error, errorInfo) => { // Log the uncaught error so it's captured by Sentry @@ -168,5 +166,5 @@ export const setupStore = (initialState = {}, config = persistConfig) => { } const { store, persistor } = setupStore() -setupListeners(store.dispatch) + export { persistor, store } diff --git a/src/send/reducers.ts b/src/send/reducers.ts index b68403a1ae3..20ecf0b7f64 100644 --- a/src/send/reducers.ts +++ b/src/send/reducers.ts @@ -14,7 +14,7 @@ export interface PaymentInfo { amount: number } -export interface State { +interface State { isSending: boolean recentRecipients: Recipient[] // Keep a list of recent (last 24 hours) payments @@ -37,7 +37,7 @@ const initialState = { export const sendReducer = ( state: State = initialState, action: ActionTypes | RehydrateAction | UpdateConfigValuesAction -) => { +): State => { switch (action.type) { case REHYDRATE: { // Ignore some persisted properties diff --git a/src/tokens/slice.ts b/src/tokens/slice.ts index ea155870624..0b740ee1dd4 100644 --- a/src/tokens/slice.ts +++ b/src/tokens/slice.ts @@ -96,7 +96,7 @@ export interface TokenBalancesWithAddress { // Create imported token interface but from the base Token type -export interface State { +interface State { tokenBalances: StoredTokenBalances loading: boolean error: boolean @@ -112,7 +112,7 @@ export function isNativeTokenBalance(tokenInfo: TokenBalance): tokenInfo is Nati return !!tokenInfo.isNative } -export const initialState = { +const initialState: State = { tokenBalances: {}, loading: false, error: false, diff --git a/src/transactions/reducer.ts b/src/transactions/reducer.ts index 858e389570e..4c80803cf94 100644 --- a/src/transactions/reducer.ts +++ b/src/transactions/reducer.ts @@ -23,7 +23,7 @@ export interface InviteTransactions { type TransactionsByNetworkId = { [networkId in NetworkId]?: TokenTransaction[] } -export interface State { +interface State { // Tracks transactions that have been initiated by the user // before they are picked up by the chain explorer and // included in the tx feed. Necessary so it shows up in the diff --git a/src/walletConnect/reducer.ts b/src/walletConnect/reducer.ts index b56c7c4d1cb..eb59c3001d0 100644 --- a/src/walletConnect/reducer.ts +++ b/src/walletConnect/reducer.ts @@ -2,7 +2,7 @@ import { SessionTypes } from '@walletconnect/types' import { Web3WalletTypes } from '@walletconnect/web3wallet' import { Actions, UserActions, WalletConnectActions } from 'src/walletConnect/actions' -export interface State { +interface State { pendingActions: Web3WalletTypes.EventArguments['session_request'][] sessions: SessionTypes.Struct[] pendingSessions: Web3WalletTypes.EventArguments['session_proposal'][] diff --git a/src/web3/reducer.ts b/src/web3/reducer.ts index c5833a0d7c1..f2978d11edb 100644 --- a/src/web3/reducer.ts +++ b/src/web3/reducer.ts @@ -2,7 +2,7 @@ import { UpdateConfigValuesAction } from 'src/app/actions' import { REHYDRATE, RehydrateAction, getRehydratePayload } from 'src/redux/persist-helper' import { ActionTypes, Actions } from 'src/web3/actions' -export interface State { +interface State { account: string | null // this is the wallet address (EOA) mtwAddress: string | null // this is the account address accountInWeb3Keystore: string | null diff --git a/test/RootStateSchema.json b/test/RootStateSchema.json index 84e805fb126..2910df10f3e 100644 --- a/test/RootStateSchema.json +++ b/test/RootStateSchema.json @@ -191,7 +191,18 @@ "action": { "anyOf": [ { - "$ref": "#/definitions/AnyAction" + "additionalProperties": false, + "description": "An *action* is a plain object that represents an intention to change the\nstate. Actions are the only way to get data into the store. Any data,\nwhether from UI events, network callbacks, or other sources such as\nWebSockets needs to eventually be dispatched as actions.\n\nActions must have a `type` field that indicates the type of action being\nperformed. Types can be defined as constants and imported from another\nmodule. These must be strings, as strings are serializable.\n\nOther than `type`, the structure of an action object is really up to you.\nIf you're interested, check out Flux Standard Action for recommendations on\nhow actions should be constructed.", + "properties": { + "type": { + "description": "the type of the action's `type` tag.", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" }, { "type": "null" @@ -319,19 +330,6 @@ ], "type": "string" }, - "AnyAction": { - "additionalProperties": {}, - "description": "An Action type which accepts any other properties.\nThis is mainly for the use of the `Reducer` type.\nThis is not part of `Action` itself to prevent types that extend `Action` from\nhaving an index signature.", - "properties": { - "type": { - "description": "the type of the action's `type` tag." - } - }, - "required": [ - "type" - ], - "type": "object" - }, "AppState": { "enum": [ "Active", diff --git a/yarn.lock b/yarn.lock index 5785877699d..33470b2086b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1040,7 +1040,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.11.tgz#7a9ba3bbe406ad6f9e8dd4da2ece453eb23a77a4" integrity sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA== @@ -3096,15 +3096,15 @@ resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.2.1.tgz#9403f51c17cae37edf870c6bc0c81c1ece5ccef8" integrity sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA== -"@reduxjs/toolkit@^1.9.7": - version "1.9.7" - resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.7.tgz#7fc07c0b0ebec52043f8cb43510cf346405f78a6" - integrity sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ== +"@reduxjs/toolkit@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.2.1.tgz#3dce4906fb33da9e0122468ef21438dd7f2277a9" + integrity sha512-8CREoqJovQW/5I4yvvijm/emUiCCmcs4Ev4XPWd4mizSO+dD3g5G6w34QK5AGeNrSH7qM8Fl66j4vuV7dpOdkw== dependencies: - immer "^9.0.21" - redux "^4.2.1" - redux-thunk "^2.4.2" - reselect "^4.1.8" + immer "^10.0.3" + redux "^5.0.1" + redux-thunk "^3.1.0" + reselect "^5.0.1" "@scure/base@~1.1.0", "@scure/base@~1.1.2", "@scure/base@~1.1.4": version "1.1.5" @@ -4942,12 +4942,12 @@ "@types/node" "*" safe-buffer "~5.1.1" -"@types/redux-mock-store@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-1.0.0.tgz#e06bad2b4ca004bdd371f432c3e48a92c1857ed9" - integrity sha512-7+H3+O8VX4Mx2HNdDLP1MSNoWp+FXfq3HDGc08kY5vxyuml7OAudO4CAQFsKsDvbU5spApJMZ6buEi/c3hKjtQ== +"@types/redux-mock-store@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-1.0.6.tgz#0a03b2655028b7cf62670d41ac1de5ca1b1f5958" + integrity sha512-eg5RDfhJTXuoJjOMyXiJbaDb1B8tfTaJixscmu+jOusj6adGC0Krntz09Tf4gJgXeCqCrM5bBMd+B7ez0izcAQ== dependencies: - redux "^4.0.0" + redux "^4.0.5" "@types/responselike@^1.0.0": version "1.0.0" @@ -10392,10 +10392,10 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= -immer@^9.0.21: - version "9.0.21" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" - integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== +immer@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/immer/-/immer-10.0.3.tgz#a8de42065e964aa3edf6afc282dfc7f7f34ae3c9" + integrity sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A== import-fresh@^2.0.0: version "2.0.0" @@ -15575,17 +15575,15 @@ redux-saga@^1.3.0: dependencies: "@redux-saga/core" "^1.3.0" -redux-thunk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" - integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== +redux-thunk@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" + integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== -redux@^4.0.0, redux@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" - integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== - dependencies: - "@babel/runtime" "^7.9.2" +redux@^4.0.0, redux@^4.0.5, redux@^5.0.0, redux@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== reflect.getprototypeof@^1.0.3: version "1.0.3" @@ -15783,6 +15781,11 @@ reselect@^4.0.0, reselect@^4.1.8: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== +reselect@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.0.tgz#c479139ab9dd91be4d9c764a7f3868210ef8cd21" + integrity sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg== + resolve-alpn@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"