diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js index 6ae1435a4..bd330a311 100644 --- a/react/features/app/components/AbstractApp.js +++ b/react/features/app/components/AbstractApp.js @@ -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 ( - + { 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)); } } diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index c75f6afe0..9afe0a606 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -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 diff --git a/react/index.native.js b/react/index.native.js index 1f983045d..6a479c96f 100644 --- a/react/index.native.js +++ b/react/index.native.js @@ -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 ( ); } diff --git a/react/index.web.js b/react/index.web.js index 5486d10a6..785afb653 100644 --- a/react/index.web.js +++ b/react/index.web.js @@ -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( - , + , document.getElementById('react')); });