Skip to content
Closed
44 changes: 3 additions & 41 deletions src/main/MainTabsScreen.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* @flow strict-local */
import React, { useContext } from 'react';
import React, { useContext, type ComponentType } from 'react';
import { Platform, View } from 'react-native';
import {
createBottomTabNavigator,
Expand All @@ -20,10 +20,8 @@ import { IconInbox, IconSettings, IconStream } from '../common/Icons';
import { OwnAvatar, OfflineNotice, ZulipStatusBar } from '../common';
import IconUnreadConversations from '../nav/IconUnreadConversations';
import ProfileScreen from '../account-info/ProfileScreen';
import { connect } from '../react-redux';
import { getHaveServerData } from '../selectors';
import styles, { ThemeContext } from '../styles';
import FullScreenLoading from '../common/FullScreenLoading';
import withHaveServerDataGate from '../withHaveServerDataGate';

export type MainTabsNavigatorParamList = {|
home: RouteParamsOf<typeof HomeScreen>,
Expand All @@ -43,35 +41,16 @@ const Tab = createBottomTabNavigator<
MainTabsNavigationProp<>,
>();

type SelectorProps = $ReadOnly<{|
haveServerData: boolean,
|}>;

type Props = $ReadOnly<{|
navigation: AppNavigationProp<'main-tabs'>,
route: RouteProp<'main-tabs', void>,

dispatch: Dispatch,
...SelectorProps,
|}>;

function MainTabsScreen(props: Props) {
const { backgroundColor } = useContext(ThemeContext);
const { haveServerData } = props;

const insets = useSafeAreaInsets();

if (!haveServerData) {
// Show a full-screen loading indicator while waiting for the
// initial fetch to complete, if we don't have potentially stale
// data to show instead. Also show it for the duration of the nav
// transition just after the user logs out (see our #4275).
//
// And avoid rendering any of our main UI, to maintain the
// guarantee that it can all rely on server data existing.
return <FullScreenLoading />;
}

return (
<View style={[styles.flexed, { backgroundColor }]}>
<View style={{ height: insets.top }} />
Expand Down Expand Up @@ -129,21 +108,4 @@ function MainTabsScreen(props: Props) {
);
}

// `connect` does something useful for us that `useSelector` doesn't
// do: it interposes a new `ReactReduxContext.Provider` component,
// which proxies subscriptions so that the descendant components only
// rerender if this one continues to say their subtree should be kept
// around. See
// https://github.com/zulip/zulip-mobile/pull/4454#discussion_r578140524
// and some discussion around
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/converting.20to.20Hooks/near/1111970
// where we describe some limits of our understanding.
//
// We found these things out while investigating an annoying crash: we
// found that `mapStateToProps` on a descendant of `MainTabsScreen`
// was running -- and throwing an uncaught error -- on logout, and
// `MainTabsScreen`'s early return on `!haveServerData` wasn't
// preventing that from happening.
export default connect(state => ({
haveServerData: getHaveServerData(state),
}))(MainTabsScreen);
export default withHaveServerDataGate<Props, ComponentType<Props>>(MainTabsScreen);
41 changes: 41 additions & 0 deletions src/withHaveServerDataGate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* @flow strict-local */
import React, { type ComponentType } from 'react';
import { compose } from 'redux';

import { connect, useSelector } from './react-redux';
import { getHaveServerData } from './selectors';
import FullScreenLoading from './common/FullScreenLoading';

export default function withHaveServerDataGate<P, C: ComponentType<P>>(Comp: C): ComponentType<P> {
return compose(
// `connect` does something useful for us that `useSelector` doesn't
// do: it interposes a new `ReactReduxContext.Provider` component,
// which proxies subscriptions so that the descendant components only
// rerender if this one continues to say their subtree should be kept
// around. See
// https://github.com/zulip/zulip-mobile/pull/4454#discussion_r578140524
// and some discussion around
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/converting.20to.20Hooks/near/1111970
// where we describe some limits of our understanding.
//
// We found these things out while investigating an annoying crash: we
// found that `mapStateToProps` on a descendant of `MainTabsScreen`
// was running -- and throwing an uncaught error -- on logout, and
// `MainTabsScreen`'s early return on `!haveServerData` wasn't
// preventing that from happening.
(connect<{||}, _, _>(): (ComponentType<P>) => ComponentType<P>),
CompInner => (props: P) =>
useSelector(getHaveServerData) ? (
<CompInner {...props} />
) : (
// Show a full-screen loading indicator while waiting for the
// initial fetch to complete, if we don't have potentially stale
// data to show instead. Also show it for the duration of the nav
// transition just after the user logs out (see our #4275).
//
// And avoid rendering any of our main UI, to maintain the
// guarantee that it can all rely on server data existing.
<FullScreenLoading />
),
)(Comp);
}