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 React, { Component } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { compose, createStore } from 'redux';
import Thunk from 'redux-thunk';
import { RouteRegistry } from '../../base/navigator'; import { RouteRegistry } from '../../base/navigator';
import { import {
localParticipantJoined, localParticipantJoined,
localParticipantLeft localParticipantLeft
} from '../../base/participants'; } from '../../base/participants';
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
import { import {
appNavigate, appNavigate,
@ -45,11 +48,18 @@ export class AbstractApp extends Component {
this.state = { this.state = {
/** /**
* The Route rendered by this App. * The Route rendered by this AbstractApp.
* *
* @type {Route} * @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 * @inheritdoc
*/ */
componentWillMount() { componentWillMount() {
const dispatch = this.props.store.dispatch; const dispatch = this._getStore().dispatch;
dispatch(appWillMount(this)); dispatch(appWillMount(this));
@ -69,6 +79,32 @@ export class AbstractApp extends Component {
this._openURL(this._getDefaultURL()); 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 * Dispose lib-jitsi-meet and remove local participant when component is
* going to be unmounted. * going to be unmounted.
@ -76,7 +112,7 @@ export class AbstractApp extends Component {
* @inheritdoc * @inheritdoc
*/ */
componentWillUnmount() { componentWillUnmount() {
const dispatch = this.props.store.dispatch; const dispatch = this._getStore().dispatch;
dispatch(localParticipantLeft()); dispatch(localParticipantLeft());
@ -94,7 +130,7 @@ export class AbstractApp extends Component {
if (route) { if (route) {
return ( return (
<Provider store = { this.props.store }> <Provider store = { this._getStore() }>
{ {
this._createElement(route.component) this._createElement(route.component)
} }
@ -142,6 +178,36 @@ export class AbstractApp extends Component {
return React.createElement(component, { ...thisProps, ...props }); 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. * 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'; 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 * Gets a Location object from the window with information about the current
* location of the document. Explicitly defined to allow extenders to * location of the document. Explicitly defined to allow extenders to
@ -204,6 +286,30 @@ export class AbstractApp extends Component {
return undefined; 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. * Navigates to a specific Route.
* *
@ -269,6 +375,6 @@ export class AbstractApp extends Component {
* @returns {void} * @returns {void}
*/ */
_openURL(url) { _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) { componentWillMount(...args) {
super.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) { _navigate(route) {
let path = route.path; 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 // 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 // segment up to the next /, ?, or #. The matched string is called a

View File

@ -1,26 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { AppRegistry, Linking } from 'react-native'; import { AppRegistry, Linking } from 'react-native';
import { createStore } from 'redux';
import Thunk from 'redux-thunk';
import config from './config'; import config from './config';
import { App } from './features/app'; 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 * React Native doesn't support specifying props to the main/root component (in
@ -83,7 +65,6 @@ class Root extends Component {
return ( return (
<App <App
config = { config } config = { config }
store = { store }
url = { this.state.url } /> url = { this.state.url } />
); );
} }

View File

@ -2,36 +2,12 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { compose, createStore } from 'redux';
import Thunk from 'redux-thunk';
import config from './config'; import config from './config';
import { App } from './features/app'; import { App } from './features/app';
import { MiddlewareRegistry, ReducerRegistry } from './features/base/redux';
const logger = require('jitsi-meet-logger').getLogger(__filename); 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. * Renders the app when the DOM tree has been loaded.
*/ */
@ -43,9 +19,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Render the main Component. // Render the main Component.
ReactDOM.render( ReactDOM.render(
<App <App config = { config } />,
config = { config }
store = { store } />,
document.getElementById('react')); document.getElementById('react'));
}); });