From 5b1672473b551babdc36aac7a1baba6d11fa53fd Mon Sep 17 00:00:00 2001 From: Boris Yankov Date: Sun, 10 Sep 2017 20:20:56 +0300 Subject: [PATCH] Rework providers and the rest root components (#1148) --- src/Providers.js | 39 ------------ src/ZulipMobile.js | 24 +++++++- src/boot/AppDataFetcher.js | 37 ++++++++++++ .../AppEventHandlers.js} | 34 +++-------- src/{nav => boot}/CompatibilityChecker.js | 0 .../StoreProvider.js} | 13 +--- src/{ => boot}/StylesProvider.js | 21 +++++-- src/boot/TranslationProvider.js | 31 ++++++++++ .../__tests}/StylesProvider-test.js | 2 +- .../__tests}/reducers-test.js | 0 src/{ => boot}/middleware.js | 2 +- src/boot/reducers.js | 59 +++++++++++++++++++ src/{ => boot}/store.js | 0 src/html/__tests__/HtmlNodeTag-test.js | 2 +- src/html/__tests__/HtmlNodeText-test.js | 11 ---- src/html/__tests__/renderHtmlChildren-test.js | 2 +- src/i18n/locale.js | 5 ++ src/nav/AppWithNavigationState.js | 10 ++-- src/nav/navReducers.js | 13 ++-- src/reducers.js | 59 ------------------- src/utils/tests.js | 4 +- 21 files changed, 196 insertions(+), 172 deletions(-) delete mode 100644 src/Providers.js create mode 100644 src/boot/AppDataFetcher.js rename src/{nav/AppContainer.js => boot/AppEventHandlers.js} (73%) rename src/{nav => boot}/CompatibilityChecker.js (100%) rename src/{StoreHydrator.js => boot/StoreProvider.js} (62%) rename src/{ => boot}/StylesProvider.js (52%) create mode 100644 src/boot/TranslationProvider.js rename src/{__tests__ => boot/__tests}/StylesProvider-test.js (81%) rename src/{__tests__ => boot/__tests}/reducers-test.js (100%) rename src/{ => boot}/middleware.js (93%) create mode 100644 src/boot/reducers.js rename src/{ => boot}/store.js (100%) delete mode 100644 src/html/__tests__/HtmlNodeText-test.js delete mode 100644 src/reducers.js diff --git a/src/Providers.js b/src/Providers.js deleted file mode 100644 index 7f84c6f12a..0000000000 --- a/src/Providers.js +++ /dev/null @@ -1,39 +0,0 @@ -/* @flow */ -import React, { PureComponent } from 'react'; -import { Text } from 'react-native'; -import { connect } from 'react-redux'; -import { IntlProvider } from 'react-intl'; - -import '../vendor/intl/intl'; -import messages from './i18n/messages'; -import StylesProvider from './StylesProvider'; -import AppContainer from './nav/AppContainer'; -import CompatibilityChecker from './nav/CompatibilityChecker'; - -require('./i18n/locale'); - -class Providers extends PureComponent { - props: { - locale: string, - theme: string, - }; - - render() { - const { locale, theme } = this.props; - - return ( - - - - - - - - ); - } -} - -export default connect(state => ({ - locale: state.settings.locale, - theme: state.settings.theme, -}))(Providers); diff --git a/src/ZulipMobile.js b/src/ZulipMobile.js index 7bdf079a45..6de54a4aea 100644 --- a/src/ZulipMobile.js +++ b/src/ZulipMobile.js @@ -3,8 +3,14 @@ import React from 'react'; import { Sentry } from 'react-native-sentry'; import '../vendor/intl/intl'; -import StoreHydrator from './StoreHydrator'; import config from './config'; +import StoreProvider from './boot/StoreProvider'; +import TranslationProvider from './boot/TranslationProvider'; +import StylesProvider from './boot/StylesProvider'; +import CompatibilityChecker from './boot/CompatibilityChecker'; +import AppEventHandlers from './boot/AppEventHandlers'; +import AppDataFetcher from './boot/AppDataFetcher'; +import AppWithNavigationState from './nav/AppWithNavigationState'; require('./i18n/locale'); @@ -14,4 +20,18 @@ if (config.enableSentry) { Sentry.config(config.sentryKey).install(); } -export default () => ; +export default () => ( + + + + + + + + + + + + + +); diff --git a/src/boot/AppDataFetcher.js b/src/boot/AppDataFetcher.js new file mode 100644 index 0000000000..65bc6ac39b --- /dev/null +++ b/src/boot/AppDataFetcher.js @@ -0,0 +1,37 @@ +/* @flow */ +import { PureComponent } from 'react'; +import { connect } from 'react-redux'; + +import { Actions } from '../types'; +import boundActions from '../boundActions'; + +class AppDataFetcher extends PureComponent { + props: { + needsInitialFetch: boolean, + actions: Actions, + children?: any, + }; + + componentWillMount = () => this.init(this.props); + + componentWillReceiveProps = nextProps => this.init(nextProps); + + init = props => { + const { actions, needsInitialFetch } = props; + + if (needsInitialFetch) { + actions.doInitialFetch(); + } + }; + + render() { + return this.props.children; + } +} + +export default connect( + state => ({ + needsInitialFetch: state.app.needsInitialFetch, + }), + boundActions, +)(AppDataFetcher); diff --git a/src/nav/AppContainer.js b/src/boot/AppEventHandlers.js similarity index 73% rename from src/nav/AppContainer.js rename to src/boot/AppEventHandlers.js index 69b30739b5..8ec49cd077 100644 --- a/src/nav/AppContainer.js +++ b/src/boot/AppEventHandlers.js @@ -5,20 +5,18 @@ import { connect } from 'react-redux'; import { Auth, Actions } from '../types'; import boundActions from '../boundActions'; -import AppWithNavigationState from './AppWithNavigationState'; import { getAuth, getNavigationIndex } from '../selectors'; import { registerAppActivity } from '../utils/activity'; -import LoadingScreen from '../start/LoadingScreen'; type Props = { auth: Auth, navIndex: number, - isHydrated: boolean, needsInitialFetch: boolean, actions: Actions, + children?: any, }; -class AppContainer extends PureComponent { +class AppEventHandlers extends PureComponent { static contextTypes = { styles: () => null, }; @@ -67,32 +65,15 @@ class AppContainer extends PureComponent { componentWillUnmount() { NetInfo.isConnected.removeEventListener('change', this.handleConnectivityChange); - AppState.addEventListener('change', this.handleAppStateChange); - AppState.addEventListener('memoryWarning', this.handleMemoryWarning); + AppState.removeEventListener('change', this.handleAppStateChange); + AppState.removeEventListener('memoryWarning', this.handleMemoryWarning); + BackHandler.removeEventListener('hardwareBackPress', this.handleBackButtonPress); } - componentWillMount = () => this.init(this.props); - - componentWillReceiveProps = nextProps => this.init(nextProps); - - init = props => { - const { actions, needsInitialFetch } = props; - - if (needsInitialFetch) { - actions.doInitialFetch(); - } - }; - render() { - const { isHydrated } = this.props; - - if (!isHydrated) { - return ; - } - return ( - + {this.props.children} ); } @@ -101,9 +82,8 @@ class AppContainer extends PureComponent { export default connect( state => ({ auth: getAuth(state), - isHydrated: state.app.isHydrated, needsInitialFetch: state.app.needsInitialFetch, navIndex: getNavigationIndex(state), }), boundActions, -)(AppContainer); +)(AppEventHandlers); diff --git a/src/nav/CompatibilityChecker.js b/src/boot/CompatibilityChecker.js similarity index 100% rename from src/nav/CompatibilityChecker.js rename to src/boot/CompatibilityChecker.js diff --git a/src/StoreHydrator.js b/src/boot/StoreProvider.js similarity index 62% rename from src/StoreHydrator.js rename to src/boot/StoreProvider.js index f4665e1dc2..722b140010 100644 --- a/src/StoreHydrator.js +++ b/src/boot/StoreProvider.js @@ -3,14 +3,9 @@ import React, { PureComponent } from 'react'; import { Provider } from 'react-redux'; import store, { restore } from './store'; -import timing from './utils/timing'; -import Providers from './Providers'; +import timing from '../utils/timing'; export default class StoreHydrator extends PureComponent { - state: { - isHydrated: boolean, - }; - componentWillMount() { timing.start('Store hydration'); restore(() => { @@ -19,10 +14,6 @@ export default class StoreHydrator extends PureComponent { } render() { - return ( - - - - ); + return {this.props.children}; } } diff --git a/src/StylesProvider.js b/src/boot/StylesProvider.js similarity index 52% rename from src/StylesProvider.js rename to src/boot/StylesProvider.js index a8b438c65e..35e0444ef0 100644 --- a/src/StylesProvider.js +++ b/src/boot/StylesProvider.js @@ -1,10 +1,11 @@ /* @flow */ -import { Children, PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { StyleSheet } from 'react-native'; +import { connect } from 'react-redux'; -import themeCreator from './styles/theme'; -import themeDark from './styles/themeDark'; -import themeLight from './styles/themeLight'; +import themeCreator from '../styles/theme'; +import themeDark from '../styles/themeDark'; +import themeLight from '../styles/themeLight'; const themeNameToObject = { default: themeLight, @@ -12,7 +13,9 @@ const themeNameToObject = { night: themeDark, }; -export default class StyleProvider extends PureComponent { +const Dummy = props => props.children; + +class StyleProvider extends PureComponent { props: { theme: string, children?: any, @@ -34,6 +37,12 @@ export default class StyleProvider extends PureComponent { } render() { - return Children.only(this.props.children); + const { children, theme } = this.props; + + return {children}; } } + +export default connect(state => ({ + theme: state.settings.theme, +}))(StyleProvider); diff --git a/src/boot/TranslationProvider.js b/src/boot/TranslationProvider.js new file mode 100644 index 0000000000..be4bd02278 --- /dev/null +++ b/src/boot/TranslationProvider.js @@ -0,0 +1,31 @@ +/* @flow */ +import React, { PureComponent } from 'react'; +import { Text } from 'react-native'; +import { connect } from 'react-redux'; +import { IntlProvider } from 'react-intl'; + +import '../../vendor/intl/intl'; +import messages from '../i18n/messages'; + +require('../i18n/locale'); + +class TranslationProvider extends PureComponent { + props: { + locale: string, + children?: any, + }; + + render() { + const { locale, children } = this.props; + + return ( + + {children} + + ); + } +} + +export default connect(state => ({ + locale: state.settings.locale, +}))(TranslationProvider); diff --git a/src/__tests__/StylesProvider-test.js b/src/boot/__tests/StylesProvider-test.js similarity index 81% rename from src/__tests__/StylesProvider-test.js rename to src/boot/__tests/StylesProvider-test.js index 30dd2adec6..d44dabedd5 100644 --- a/src/__tests__/StylesProvider-test.js +++ b/src/boot/__tests/StylesProvider-test.js @@ -1,7 +1,7 @@ import React from 'react'; import { View } from 'react-native'; -import { rendererWithStyle } from '../utils/tests'; +import { rendererWithStyle } from '../../utils/tests'; describe('StylesProvider', () => { test('renders', () => { diff --git a/src/__tests__/reducers-test.js b/src/boot/__tests/reducers-test.js similarity index 100% rename from src/__tests__/reducers-test.js rename to src/boot/__tests/reducers-test.js diff --git a/src/middleware.js b/src/boot/middleware.js similarity index 93% rename from src/middleware.js rename to src/boot/middleware.js index afc15d2f10..6f0a422996 100644 --- a/src/middleware.js +++ b/src/boot/middleware.js @@ -4,7 +4,7 @@ import { createLogger } from 'redux-logger'; import { REHYDRATE } from 'redux-persist/constants'; import createActionBuffer from 'redux-action-buffer'; -import config from './config'; +import config from '../config'; const middleware = [thunk, createActionBuffer(REHYDRATE)]; diff --git a/src/boot/reducers.js b/src/boot/reducers.js new file mode 100644 index 0000000000..53121c9ccd --- /dev/null +++ b/src/boot/reducers.js @@ -0,0 +1,59 @@ +/* @flow */ +import { combineReducers } from 'redux'; + +import config from '../config'; +import { enableBatching, logSlowReducers } from '../utils/redux'; + +import accounts from '../account/accountReducers'; +import alertWords from '../alertWords/alertWordsReducer'; +import app from '../app/appReducers'; +import caughtUp from '../caughtup/caughtUpReducers'; +import chat from '../chat/chatReducers'; +import fetching from '../chat/fetchingReducers'; +import flags from '../chat/flagsReducers'; +import mute from '../mute/muteReducers'; +import nav from '../nav/navReducers'; +import realm from '../realm/realmReducers'; +import outbox from '../outbox/outboxReducers'; +import drafts from '../drafts/draftsReducers'; +import settings from '../settings/settingsReducers'; +import streams from '../streams/streamsReducers'; +import subscriptions from '../subscriptions/subscriptionsReducers'; +import typing from '../typing/typingReducers'; +import unreadStreams from '../unread/unreadStreamsReducers'; +import unreadPms from '../unread/unreadPmsReducers'; +import unreadHuddles from '../unread/unreadHuddlesReducers'; +import unreadMentions from '../unread/unreadMentionsReducers'; +import users from '../users/usersReducers'; +import presence from '../presence/presenceReducers'; + +const reducers = { + accounts, + alertWords, + app, + caughtUp, + chat, + fetching, + drafts, + flags, + mute, + nav, + presence, + realm, + outbox, + settings, + streams, + subscriptions, + typing, + unread: combineReducers({ + streams: unreadStreams, + pms: unreadPms, + huddles: unreadHuddles, + mentions: unreadMentions, + }), + users, +}; + +export default enableBatching( + combineReducers(config.enableReduxSlowReducerWarnings ? logSlowReducers(reducers) : reducers), +); diff --git a/src/store.js b/src/boot/store.js similarity index 100% rename from src/store.js rename to src/boot/store.js diff --git a/src/html/__tests__/HtmlNodeTag-test.js b/src/html/__tests__/HtmlNodeTag-test.js index 550270cb9a..be122f3561 100644 --- a/src/html/__tests__/HtmlNodeTag-test.js +++ b/src/html/__tests__/HtmlNodeTag-test.js @@ -2,7 +2,7 @@ import React from 'react'; import ReactTestRenderer from 'react-test-renderer'; import { Provider } from 'react-redux'; -import store from '../../store'; +import store from '../../boot/store'; import HtmlNodeTag from '../HtmlNodeTag'; describe('HtmlNodeTag', () => { diff --git a/src/html/__tests__/HtmlNodeText-test.js b/src/html/__tests__/HtmlNodeText-test.js deleted file mode 100644 index a23a091769..0000000000 --- a/src/html/__tests__/HtmlNodeText-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -import { rendererWithStyle } from '../../utils/tests'; -import HtmlNodeText from '../HtmlNodeText'; - -describe('HtmlNodeText', () => { - test('renders plain text', () => { - const rendered = rendererWithStyle().toJSON(); - expect(rendered.children).toEqual(['hello']); - }); -}); diff --git a/src/html/__tests__/renderHtmlChildren-test.js b/src/html/__tests__/renderHtmlChildren-test.js index bea0164306..0eccb134df 100644 --- a/src/html/__tests__/renderHtmlChildren-test.js +++ b/src/html/__tests__/renderHtmlChildren-test.js @@ -3,7 +3,7 @@ import { View } from 'react-native'; import ReactTestRenderer from 'react-test-renderer'; import { Provider } from 'react-redux'; -import store from '../../store'; +import store from '../../boot/store'; import htmlToDomTree from '../htmlToDomTree'; import renderHtmlChildren from '../renderHtmlChildren'; diff --git a/src/i18n/locale.js b/src/i18n/locale.js index 0bf1075ed6..818f5a3d60 100644 --- a/src/i18n/locale.js +++ b/src/i18n/locale.js @@ -4,6 +4,7 @@ import { addLocaleData } from 'react-intl'; import ar from 'react-intl/locale-data/ar'; import bg from 'react-intl/locale-data/bg'; +import ca from 'react-intl/locale-data/ca'; import cs from 'react-intl/locale-data/cs'; import de from 'react-intl/locale-data/de'; import en from 'react-intl/locale-data/en'; @@ -11,6 +12,7 @@ import es from 'react-intl/locale-data/es'; import fr from 'react-intl/locale-data/fr'; import hi from 'react-intl/locale-data/hi'; import hu from 'react-intl/locale-data/hu'; +import id from 'react-intl/locale-data/id'; import it from 'react-intl/locale-data/it'; import ja from 'react-intl/locale-data/ja'; import ko from 'react-intl/locale-data/ko'; @@ -22,11 +24,13 @@ import ru from 'react-intl/locale-data/ru'; import sr from 'react-intl/locale-data/sr'; import sv from 'react-intl/locale-data/sv'; import ta from 'react-intl/locale-data/ta'; +import tr from 'react-intl/locale-data/tr'; import zh from 'react-intl/locale-data/zh'; [ ar, bg, + ca, cs, de, en, @@ -45,5 +49,6 @@ import zh from 'react-intl/locale-data/zh'; sr, sv, ta, + tr, zh, ].forEach(locale => addLocaleData(locale)); diff --git a/src/nav/AppWithNavigationState.js b/src/nav/AppWithNavigationState.js index 26095363c8..ac2b0eb2c3 100644 --- a/src/nav/AppWithNavigationState.js +++ b/src/nav/AppWithNavigationState.js @@ -5,15 +5,13 @@ import { connect } from 'react-redux'; import AppNavigator from './AppNavigator'; -const AppWithNavigationState = props => ( +export default connect(state => ({ + nav: state.nav, +}))(props => ( -); - -export default connect(state => ({ - nav: state.nav, -}))(AppWithNavigationState); +)); diff --git a/src/nav/navReducers.js b/src/nav/navReducers.js index 954cb02aa9..3b1a8ed7e9 100644 --- a/src/nav/navReducers.js +++ b/src/nav/navReducers.js @@ -15,24 +15,27 @@ import { } from '../actionConstants'; export default ( - state: NavigationState = getStateForRoute('realm'), + state: NavigationState = getStateForRoute('loading'), action: Action, ): NavigationState => { switch (action.type) { case REHYDRATE: return getStateForRoute(getInitialRoute(action.payload)); + case RESET_NAVIGATION: case ACCOUNT_SWITCH: return getStateForRoute(getInitialRoute(state)); - case SET_AUTH_TYPE: { + + case SET_AUTH_TYPE: return AppNavigator.router.getStateForAction(navigateToAuth(action.authType), state); - } + case LOGIN_SUCCESS: case INITIAL_FETCH_COMPLETE: return getStateForRoute('main'); - case LOGOUT: { + + case LOGOUT: return AppNavigator.router.getStateForAction(navigateToAccountPicker(), state); - } + default: return AppNavigator.router.getStateForAction(action, state); } diff --git a/src/reducers.js b/src/reducers.js deleted file mode 100644 index 029abf3a77..0000000000 --- a/src/reducers.js +++ /dev/null @@ -1,59 +0,0 @@ -/* @flow */ -import { combineReducers } from 'redux'; - -import config from './config'; -import { enableBatching, logSlowReducers } from './utils/redux'; - -import accounts from './account/accountReducers'; -import alertWords from './alertWords/alertWordsReducer'; -import app from './app/appReducers'; -import caughtUp from './caughtup/caughtUpReducers'; -import chat from './chat/chatReducers'; -import fetching from './chat/fetchingReducers'; -import flags from './chat/flagsReducers'; -import mute from './mute/muteReducers'; -import nav from './nav/navReducers'; -import realm from './realm/realmReducers'; -import outbox from './outbox/outboxReducers'; -import drafts from './drafts/draftsReducers'; -import settings from './settings/settingsReducers'; -import streams from './streams/streamsReducers'; -import subscriptions from './subscriptions/subscriptionsReducers'; -import typing from './typing/typingReducers'; -import unreadStreams from './unread/unreadStreamsReducers'; -import unreadPms from './unread/unreadPmsReducers'; -import unreadHuddles from './unread/unreadHuddlesReducers'; -import unreadMentions from './unread/unreadMentionsReducers'; -import users from './users/usersReducers'; -import presence from './presence/presenceReducers'; - -const reducers = { - accounts, - alertWords, - app, - caughtUp, - chat, - fetching, - drafts, - flags, - mute, - nav, - presence, - realm, - outbox, - settings, - streams, - subscriptions, - typing, - unread: combineReducers({ - streams: unreadStreams, - pms: unreadPms, - huddles: unreadHuddles, - mentions: unreadMentions, - }), - users, -}; - -export default enableBatching( - combineReducers(config.enableReduxSlowReducerWarnings ? logSlowReducers(reducers) : reducers), -); diff --git a/src/utils/tests.js b/src/utils/tests.js index 6aef32eba7..c68957b6e1 100644 --- a/src/utils/tests.js +++ b/src/utils/tests.js @@ -2,8 +2,8 @@ import React from 'react'; import { Provider } from 'react-redux'; import renderer from 'react-test-renderer'; -import StylesProvider from '../StylesProvider'; -import store from '../store'; +import StylesProvider from '../boot/StylesProvider'; +import store from '../boot/store'; export const rendererWithStore = WrappedComponent => renderer.create({WrappedComponent});