Skip to content

Commit

Permalink
Convert App.js => App.tsx (#5792)
Browse files Browse the repository at this point in the history
* .

* undo tsconfig change and create routes.tsx with platform selector

* chore: merge

* yep

* fix

* rm authorize

* change initial route context initial value

* wrap in usecallbacks
  • Loading branch information
walmat committed Aug 6, 2024
1 parent fdcdd09 commit 0d09b10
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 161 deletions.
261 changes: 106 additions & 155 deletions src/App.js → src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import './languages';
import * as Sentry from '@sentry/react-native';
import React, { Component } from 'react';
import { AppRegistry, AppState, Dimensions, InteractionManager, Linking, LogBox, View } from 'react-native';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { AppRegistry, AppState, AppStateStatus, Dimensions, InteractionManager, Linking, LogBox, View } from 'react-native';
import branch from 'react-native-branch';

import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
Expand All @@ -14,32 +15,25 @@ import { OfflineToast } from './components/toasts';
import { designSystemPlaygroundEnabled, reactNativeDisableYellowBox, showNetworkRequests, showNetworkResponses } from './config/debug';
import monitorNetwork from './debugging/network';
import { Playground } from './design-system/playground/Playground';
import { TransactionType } from './entities';
import appEvents from './handlers/appEvents';
import handleDeeplink from './handlers/deeplinks';
import { runWalletBackupStatusChecks } from './handlers/walletReadyEvents';
import { getIsHardhatConnected, isL2Network } from './handlers/web3';
import RainbowContextWrapper from './helpers/RainbowContext';
import isTestFlight from './helpers/isTestFlight';
import networkTypes from './helpers/networkTypes';
import * as keychain from '@/model/keychain';
import { loadAddress } from './model/wallet';
import { Navigation } from './navigation';
// eslint-disable-next-line import/no-unresolved
import RoutesComponent from './navigation/Routes';
import { PerformanceContextMap } from './performance/PerformanceContextMap';
import { PerformanceTracking } from './performance/tracking';
import { PerformanceMetrics } from './performance/tracking/types/PerformanceMetrics';
import { PersistQueryClientProvider, persistOptions, queryClient } from './react-query';
import store from './redux/store';
import { walletConnectLoadState } from './redux/walletconnect';
import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery';
import { MainThemeProvider } from './theme/ThemeContext';
import { ethereumUtils } from './utils';
import { branchListener } from './utils/branch';
import { addressKey } from './utils/keychainConstants';
import { SharedValuesProvider } from '@/helpers/SharedValuesContext';
import { InitialRouteContext } from '@/navigation/initialRoute';
import { InitialRoute, InitialRouteContext } from '@/navigation/initialRoute';
import Routes from '@/navigation/routesNames';
import { Portal } from '@/react-native-cool-modals/Portal';
import { NotificationsHandler } from '@/notifications/NotificationsHandler';
Expand All @@ -50,11 +44,13 @@ import * as ls from '@/storage';
import { migrate } from '@/migrations';
import { initListeners as initWalletConnectListeners } from '@/walletConnect';
import { saveFCMToken } from '@/notifications/tokens';
import branch from 'react-native-branch';
import { initializeReservoirClient } from '@/resources/reservoir/client';
import { ReviewPromptAction } from '@/storage/schema';
import { handleReviewPromptAction } from '@/utils/reviewAlert';
import { initializeRemoteConfig } from '@/model/remoteConfig';
import { NavigationContainerRef } from '@react-navigation/native';
import { RootStackParamList } from './navigation/types';
import { Address } from 'viem';
import { IS_DEV } from './env';
import { checkIdentifierOnLaunch } from './model/backup';

Expand All @@ -67,179 +63,132 @@ enableScreens();

const containerStyle = { flex: 1 };

class OldApp extends Component {
state = {
appState: AppState.currentState,
initialRoute: null,
eventSubscription: null,
};

/**
* There's a race condition in Branch's RN SDK. From a cold start, Branch
* doesn't always handle an initial URL, so we need to check for it here and
* then pass it to Branch to do its thing.
*
* @see https://github.com/BranchMetrics/react-native-branch-deep-linking-attribution/issues/673#issuecomment-1220974483
*/
async setupDeeplinking() {
interface AppProps {
walletReady: boolean;
}

function App({ walletReady }: AppProps) {
const [appState, setAppState] = useState(AppState.currentState);
const [initialRoute, setInitialRoute] = useState<InitialRoute>(null);
const eventSubscription = useRef<ReturnType<typeof AppState.addEventListener> | null>(null);
const branchListenerRef = useRef<ReturnType<typeof branch.subscribe> | null>(null);
const navigatorRef = useRef<NavigationContainerRef<RootStackParamList> | null>(null);

const setupDeeplinking = useCallback(async () => {
const initialUrl = await Linking.getInitialURL();

// main Branch handler
this.branchListener = await branchListener(url => {
branchListenerRef.current = await branchListener(url => {
logger.debug(`Branch: listener called`, {}, logger.DebugContext.deeplinks);

try {
handleDeeplink(url, this.state.initialRoute);
} catch (e) {
logger.error(new RainbowError('Error opening deeplink'), {
message: e.message,
url,
});
handleDeeplink(url, initialRoute);
} catch (error) {
if (error instanceof Error) {
logger.error(new RainbowError('Error opening deeplink'), {
message: error.message,
url,
});
} else {
logger.error(new RainbowError('Error opening deeplink'), {
message: 'Unknown error',
url,
});
}
}
});

// if we have an initial URL, pass it to Branch
if (initialUrl) {
logger.debug(`App: has initial URL, opening with Branch`, { initialUrl });
branch.openURL(initialUrl);
}
}

async componentDidMount() {
if (!__DEV__ && isTestFlight) {
logger.info(`Test flight usage - ${isTestFlight}`);
}

this.identifyFlow();
const eventSub = AppState?.addEventListener('change', this?.handleAppStateChange);
this.setState({ eventSubscription: eventSub });
appEvents.on('transactionConfirmed', this.handleTransactionConfirmed);

const p1 = analyticsV2.initializeRudderstack();
const p2 = this.setupDeeplinking();
const p3 = saveFCMToken();
await Promise.all([p1, p2, p3]);

/**
* Needs to be called AFTER FCM token is loaded
*/
initWalletConnectListeners();

PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent);
analyticsV2.track(analyticsV2.event.applicationDidMount);
}

componentDidUpdate(prevProps) {
if (!prevProps.walletReady && this.props.walletReady) {
// Everything we need to do after the wallet is ready goes here
logger.info('✅ Wallet ready!');
runWalletBackupStatusChecks();
}
}

componentWillUnmount() {
this.state.eventSubscription.remove();
this.branchListener();
}
}, [initialRoute]);

identifyFlow = async () => {
const identifyFlow = useCallback(async () => {
const address = await loadAddress();

if (address) {
setTimeout(() => {
InteractionManager.runAfterInteractions(() => {
handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall);
});
}, 10_000);

checkIdentifierOnLaunch();
InteractionManager.runAfterInteractions(checkIdentifierOnLaunch);
}

const initialRoute = address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN;
this.setState({ initialRoute });
PerformanceContextMap.set('initialRoute', initialRoute);
};

handleAppStateChange = async nextAppState => {
// Restore WC connectors when going from BG => FG
if (this.state.appState === 'background' && nextAppState === 'active') {
store.dispatch(walletConnectLoadState());
}
this.setState({ appState: nextAppState });
setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN);
PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN);
}, []);

analyticsV2.track(analyticsV2.event.appStateChange, {
category: 'app state',
label: nextAppState,
});
};
const handleAppStateChange = useCallback(
(nextAppState: AppStateStatus) => {
if (appState === 'background' && nextAppState === 'active') {
store.dispatch(walletConnectLoadState());
}
setAppState(nextAppState);
analyticsV2.track(analyticsV2.event.appStateChange, {
category: 'app state',
label: nextAppState,
});
},
[appState]
);

handleNavigatorRef = navigatorRef => {
this.navigatorRef = navigatorRef;
Navigation.setTopLevelNavigator(navigatorRef);
};
const handleNavigatorRef = useCallback((ref: NavigationContainerRef<RootStackParamList>) => {
navigatorRef.current = ref;
Navigation.setTopLevelNavigator(ref);
}, []);

handleTransactionConfirmed = tx => {
const network = tx.chainId ? ethereumUtils.getNetworkFromChainId(tx.chainId) : tx.network || networkTypes.mainnet;
const isL2 = isL2Network(network);
useEffect(() => {
if (!__DEV__ && isTestFlight) {
logger.info(`Test flight usage - ${isTestFlight}`);
}
identifyFlow();
eventSubscription.current = AppState.addEventListener('change', handleAppStateChange);

const connectedToHardhat = getIsHardhatConnected();
const p1 = analyticsV2.initializeRudderstack();
const p2 = setupDeeplinking();
const p3 = saveFCMToken();
Promise.all([p1, p2, p3]).then(() => {
initWalletConnectListeners();
PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent);
analyticsV2.track(analyticsV2.event.applicationDidMount);
});

const updateBalancesAfter = (timeout, isL2, network) => {
const { accountAddress, nativeCurrency } = store.getState().settings;
setTimeout(() => {
logger.debug('Reloading balances for network', network);
if (isL2) {
if (tx.internalType !== TransactionType.authorize) {
// for swaps, we don't want to trigger update balances on unlock txs
queryClient.invalidateQueries({
queryKey: userAssetsQueryKey({
address: accountAddress,
currency: nativeCurrency,
connectedToHardhat,
}),
});
}
} else {
queryClient.invalidateQueries({
queryKey: userAssetsQueryKey({
address: accountAddress,
currency: nativeCurrency,
connectedToHardhat,
}),
});
}
}, timeout);
return () => {
eventSubscription.current?.remove();
branchListenerRef.current?.();
};
logger.debug('reloading balances soon...');
updateBalancesAfter(2000, isL2, network);
updateBalancesAfter(isL2 ? 10000 : 5000, isL2, network);
};

render() {
return (
<Portal>
<View style={containerStyle}>
{this.state.initialRoute && (
<InitialRouteContext.Provider value={this.state.initialRoute}>
<RoutesComponent ref={this.handleNavigatorRef} />
<PortalConsumer />
</InitialRouteContext.Provider>
)}
<OfflineToast />
</View>
<NotificationsHandler walletReady={this.props.walletReady} />
</Portal>
);
}
}, [handleAppStateChange, identifyFlow, setupDeeplinking]);

useEffect(() => {
if (walletReady) {
logger.info('✅ Wallet ready!');
runWalletBackupStatusChecks();
}
}, [walletReady]);

return (
<Portal>
<View style={containerStyle}>
{initialRoute && (
<InitialRouteContext.Provider value={initialRoute}>
<RoutesComponent ref={handleNavigatorRef} />
<PortalConsumer />
</InitialRouteContext.Provider>
)}
<OfflineToast />
</View>
<NotificationsHandler walletReady={walletReady} />
</Portal>
);
}

const OldAppWithRedux = connect(state => ({
walletReady: state.appState.walletReady,
}))(OldApp);
export type AppStore = typeof store;
export type RootState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];

function App() {
return <OldAppWithRedux />;
}
const AppWithRedux = connect<unknown, AppDispatch, unknown, RootState>(state => ({
walletReady: state.appState.walletReady,
}))(App);

function Root() {
const [initializing, setInitializing] = React.useState(true);
Expand All @@ -253,7 +202,7 @@ function Root() {
const [deviceId, deviceIdWasJustCreated] = await getOrCreateDeviceId();
const currentWalletAddress = await keychain.loadString(addressKey);
const currentWalletAddressHash =
typeof currentWalletAddress === 'string' ? securelyHashWalletAddress(currentWalletAddress) : undefined;
typeof currentWalletAddress === 'string' ? securelyHashWalletAddress(currentWalletAddress as Address) : undefined;

Sentry.setUser({
id: deviceId,
Expand Down Expand Up @@ -336,6 +285,7 @@ function Root() {
}, [setInitializing]);

return initializing ? null : (
// @ts-expect-error - Property 'children' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Provider<AppStateUpdateAction | ChartsUpdateAction | ContactsAction | ... 13 more ... | WalletsAction>> & Readonly<...>'
<ReduxProvider store={store}>
<RecoilRoot>
<PersistQueryClientProvider client={queryClient} persistOptions={persistOptions}>
Expand All @@ -345,7 +295,7 @@ function Root() {
<RainbowContextWrapper>
<SharedValuesProvider>
<ErrorBoundary>
<App />
<AppWithRedux walletReady={false} />
</ErrorBoundary>
</SharedValuesProvider>
</RainbowContextWrapper>
Expand All @@ -362,6 +312,7 @@ function Root() {
const RootWithSentry = Sentry.wrap(Root);

const PlaygroundWithReduxStore = () => (
// @ts-expect-error - Property 'children' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Provider<AppStateUpdateAction | ChartsUpdateAction | ContactsAction | ... 13 more ... | WalletsAction>> & Readonly<...>'
<ReduxProvider store={store}>
<Playground />
</ReduxProvider>
Expand Down
1 change: 1 addition & 0 deletions src/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type {
RainbowTransaction,
ZerionTransaction,
ZerionTransactionChange,
transactionTypes,
} from './transactions';
export { GasFeeTypes, TransactionDirection, TransactionDirections, TransactionStatus, TransactionStatusTypes } from './transactions';
export type { EthereumAddress } from './wallet';
Expand Down
1 change: 1 addition & 0 deletions src/entities/transactions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type { NewTransaction, NewTransactionOrAddCashTransaction, RainbowTransaction } from './transaction';
export { default as TransactionStatusTypes, TransactionStatus } from './transactionStatus';
export { transactionTypes } from './transactionType';

export type { ZerionTransaction, ZerionTransactionChange } from './zerionTransaction';
export { default as TransactionDirections, TransactionDirection } from './transactionDirection';
Expand Down
2 changes: 1 addition & 1 deletion src/graphql/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ exports.config = {
headers: {},
},
},
};
};
6 changes: 6 additions & 0 deletions src/navigation/Routes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Platform } from 'react-native';

export default Platform.select({
ios: require('./Routes.ios'),
android: require('./Routes.android'),
});
Loading

0 comments on commit 0d09b10

Please sign in to comment.