Remove duplication

The files react/index.native.js and react/index.web.js ended up having
very similar source code related to initializing the Redux store. Remove
the duplication.

Additionally, I always wanted the App React Component to be consumed
without the need to provide a Redux store to it.
This commit is contained in:
Lyubomir Marinov 2017-01-26 15:29:28 -06:00
parent 3fd33d0f50
commit 49b3b49f3e
4 changed files with 115 additions and 54 deletions

View File

@ -1,11 +1,14 @@
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { compose, createStore } from 'redux';
import Thunk from 'redux-thunk';
import { RouteRegistry } from '../../base/navigator';
import {
localParticipantJoined,
localParticipantLeft
} from '../../base/participants';
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
import {
appNavigate,
@ -45,11 +48,18 @@ export class AbstractApp extends Component {
this.state = {
/**
* The Route rendered by this App.
* The Route rendered by this AbstractApp.
*
* @type {Route}
*/
route: undefined
route: undefined,
/**
* The Redux store used by this AbstractApp.
*
* @type {Store}
*/
store: this._maybeCreateStore(props)
};
}
@ -60,7 +70,7 @@ export class AbstractApp extends Component {
* @inheritdoc
*/
componentWillMount() {
const dispatch = this.props.store.dispatch;
const dispatch = this._getStore().dispatch;
dispatch(appWillMount(this));
@ -69,6 +79,32 @@ export class AbstractApp extends Component {
this._openURL(this._getDefaultURL());
}
/**
* Notifies this mounted React Component that it will receive new props.
* Makes sure that this AbstractApp has a Redux store to use.
*
* @inheritdoc
* @param {Object} nextProps - The read-only React Component props that this
* instance will receive.
* @returns {void}
*/
componentWillReceiveProps(nextProps) {
// The consumer of this AbstractApp did not provide a Redux store.
if (typeof nextProps.store === 'undefined'
// The consumer of this AbstractApp did provide a Redux store
// before. Which means that the consumer changed their mind. In
// such a case this instance should create its own internal
// Redux store. If the consumer did not provide a Redux store
// before, then this instance is using its own internal Redux
// store already.
&& typeof this.props.store !== 'undefined') {
this.setState({
store: this._maybeCreateStore(nextProps)
});
}
}
/**
* Dispose lib-jitsi-meet and remove local participant when component is
* going to be unmounted.
@ -76,7 +112,7 @@ export class AbstractApp extends Component {
* @inheritdoc
*/
componentWillUnmount() {
const dispatch = this.props.store.dispatch;
const dispatch = this._getStore().dispatch;
dispatch(localParticipantLeft());
@ -94,7 +130,7 @@ export class AbstractApp extends Component {
if (route) {
return (
<Provider store = { this.props.store }>
<Provider store = { this._getStore() }>
{
this._createElement(route.component)
}
@ -142,6 +178,36 @@ export class AbstractApp extends Component {
return React.createElement(component, { ...thisProps, ...props });
}
/**
* Initializes a new Redux store instance suitable for use by
* this AbstractApp.
*
* @private
* @returns {Store} - A new Redux store instance suitable for use by
* this AbstractApp.
*/
_createStore() {
// Create combined reducer from all reducers in ReducerRegistry.
const reducer = ReducerRegistry.combineReducers();
// Apply all registered middleware from the MiddlewareRegistry and
// additional 3rd party middleware:
// - Thunk - allows us to dispatch async actions easily. For more info
// @see https://github.com/gaearon/redux-thunk.
let middleware = MiddlewareRegistry.applyMiddleware(Thunk);
// Try to enable Redux DevTools Chrome extension in order to make it
// available for the purposes of facilitating development.
let devToolsExtension;
if (typeof window === 'object'
&& (devToolsExtension = window.devToolsExtension)) {
middleware = compose(middleware, devToolsExtension());
}
return createStore(reducer, middleware);
}
/**
* Gets the default URL to be opened when this App mounts.
*
@ -189,6 +255,22 @@ export class AbstractApp extends Component {
return 'https://meet.jit.si';
}
/**
* Gets the Redux store used by this AbstractApp.
*
* @protected
* @returns {Store} - The Redux store used by this AbstractApp.
*/
_getStore() {
let store = this.state.store;
if (typeof store === 'undefined') {
store = this.props.store;
}
return store;
}
/**
* Gets a Location object from the window with information about the current
* location of the document. Explicitly defined to allow extenders to
@ -204,6 +286,30 @@ export class AbstractApp extends Component {
return undefined;
}
/**
* Creates a Redux store to be used by this AbstractApp if such as store is
* not defined by the consumer of this AbstractApp through its
* read-only React Component props.
*
* @param {Object} props - The read-only React Component props that will
* eventually be received by this AbstractApp.
* @private
* @returns {Store} - The Redux store to be used by this AbstractApp.
*/
_maybeCreateStore(props) {
// The application Jitsi Meet is architected with Redux. However, I do
// not want consumers of the App React Component to be forced into
// dealing with Redux. If the consumer did not provide an external Redux
// store, utilize an internal Redux store.
let store = props.store;
if (typeof store === 'undefined') {
store = this._createStore();
}
return store;
}
/**
* Navigates to a specific Route.
*
@ -269,6 +375,6 @@ export class AbstractApp extends Component {
* @returns {void}
*/
_openURL(url) {
this.props.store.dispatch(appNavigate(url));
this._getStore().dispatch(appNavigate(url));
}
}

View File

@ -22,7 +22,7 @@ export class App extends AbstractApp {
componentWillMount(...args) {
super.componentWillMount(...args);
this.props.store.dispatch(appInit());
this._getStore().dispatch(appInit());
}
/**
@ -44,7 +44,7 @@ export class App extends AbstractApp {
*/
_navigate(route) {
let path = route.path;
const store = this.props.store;
const store = this._getStore();
// The syntax :room bellow is defined by react-router. It "matches a URL
// segment up to the next /, ?, or #. The matched string is called a

View File

@ -1,26 +1,8 @@
import React, { Component } from 'react';
import { AppRegistry, Linking } from 'react-native';
import { createStore } from 'redux';
import Thunk from 'redux-thunk';
import config from './config';
import { App } from './features/app';
import {
MiddlewareRegistry,
ReducerRegistry
} from './features/base/redux';
// Create combined reducer from all reducers in registry.
const reducer = ReducerRegistry.combineReducers();
// Apply all registered middleware from the MiddlewareRegistry + additional
// 3rd party middleware:
// - Thunk - allows us to dispatch async actions easily. For more info
// @see https://github.com/gaearon/redux-thunk.
const middleware = MiddlewareRegistry.applyMiddleware(Thunk);
// Create Redux store with our reducer and middleware.
const store = createStore(reducer, middleware);
/**
* React Native doesn't support specifying props to the main/root component (in
@ -83,7 +65,6 @@ class Root extends Component {
return (
<App
config = { config }
store = { store }
url = { this.state.url } />
);
}

View File

@ -2,36 +2,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { compose, createStore } from 'redux';
import Thunk from 'redux-thunk';
import config from './config';
import { App } from './features/app';
import { MiddlewareRegistry, ReducerRegistry } from './features/base/redux';
const logger = require('jitsi-meet-logger').getLogger(__filename);
// Create combined reducer from all reducers in registry.
const reducer = ReducerRegistry.combineReducers();
// Apply all registered middleware from the MiddlewareRegistry + additional
// 3rd party middleware:
// - Thunk - allows us to dispatch async actions easily. For more info
// @see https://github.com/gaearon/redux-thunk.
let middleware = MiddlewareRegistry.applyMiddleware(Thunk);
// Try to enable Redux DevTools Chrome extension in order to make it available
// for the purposes of facilitating development.
let devToolsExtension;
if (typeof window === 'object'
&& (devToolsExtension = window.devToolsExtension)) {
middleware = compose(middleware, devToolsExtension());
}
// Create Redux store with our reducer and middleware.
const store = createStore(reducer, middleware);
/**
* Renders the app when the DOM tree has been loaded.
*/
@ -43,9 +19,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Render the main Component.
ReactDOM.render(
<App
config = { config }
store = { store } />,
<App config = { config } />,
document.getElementById('react'));
});