diff --git a/react/features/welcome/components/AbstractWelcomePage.js b/react/features/welcome/components/AbstractWelcomePage.js
index 309668c46..994ef5637 100644
--- a/react/features/welcome/components/AbstractWelcomePage.js
+++ b/react/features/welcome/components/AbstractWelcomePage.js
@@ -48,6 +48,7 @@ export class AbstractWelcomePage extends Component {
this.state = {
animateTimeoutId: null,
generatedRoomname: '',
+ joining: false,
room: '',
roomPlaceholder: '',
updateTimeoutId: null
@@ -62,7 +63,18 @@ export class AbstractWelcomePage extends Component {
}
/**
- * This method is executed when component receives new properties.
+ * Implements React's {@link Component#componentWillMount()}. Invoked
+ * immediately before mounting occurs.
+ *
+ * @inheritdoc
+ */
+ componentWillMount() {
+ this._mounted = true;
+ }
+
+ /**
+ * Implements React's {@link Component#componentWillReceiveProps()}. Invoked
+ * before this mounted component receives new props.
*
* @inheritdoc
* @param {Object} nextProps - New props component will receive.
@@ -72,12 +84,14 @@ export class AbstractWelcomePage extends Component {
}
/**
- * This method is executed when method will be unmounted from DOM.
+ * Implements React's {@link Component#componentWillUnmount()}. Invoked
+ * immediately before this component is unmounted and destroyed.
*
* @inheritdoc
*/
componentWillUnmount() {
this._clearTimeouts();
+ this._mounted = false;
}
/**
@@ -128,7 +142,7 @@ export class AbstractWelcomePage extends Component {
* otherwise, false.
*/
_isJoinDisabled() {
- return !isRoomValid(this.state.room);
+ return this.state.joining || !isRoomValid(this.state.room);
}
/**
@@ -141,7 +155,18 @@ export class AbstractWelcomePage extends Component {
_onJoin() {
const room = this.state.room || this.state.generatedRoomname;
- room && this.props.dispatch(appNavigate(room));
+ if (room) {
+ this.setState({ joining: true });
+
+ // By the time the Promise of appNavigate settles, this component
+ // may have already been unmounted.
+ const onAppNavigateSettled = () => {
+ this._mounted && this.setState({ joining: false });
+ };
+
+ this.props.dispatch(appNavigate(room))
+ .then(onAppNavigateSettled, onAppNavigateSettled);
+ }
}
/**
diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js
index a7ee46458..723bf2c5a 100644
--- a/react/features/welcome/components/WelcomePage.native.js
+++ b/react/features/welcome/components/WelcomePage.native.js
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { MEDIA_TYPE } from '../../base/media';
-import { Link, Text } from '../../base/react';
+import { Link, LoadingIndicator, Text } from '../../base/react';
import { ColorPalette } from '../../base/styles';
import { createDesiredLocalTracks } from '../../base/tracks';
@@ -41,18 +41,24 @@ class WelcomePage extends AbstractWelcomePage {
static propTypes = AbstractWelcomePage.propTypes;
/**
- * Creates a video track if not already available.
+ * Implements React's {@link Component#componentWillMount()}. Invoked
+ * immediately before mounting occurs. Creates a local video track if none
+ * is available.
*
* @inheritdoc
* @returns {void}
*/
componentWillMount() {
+ super.componentWillMount();
+
this.props.dispatch(createDesiredLocalTracks(MEDIA_TYPE.VIDEO));
}
/**
- * Renders a prompt for entering a room name.
+ * Implements React's {@link Component#render()}. Renders a prompt for
+ * entering a room name.
*
+ * @inheritdoc
* @returns {ReactElement}
*/
render() {
@@ -75,16 +81,9 @@ class WelcomePage extends AbstractWelcomePage {
style = { styles.textInput }
underlineColorAndroid = 'transparent'
value = { this.state.room } />
-
-
- { t('welcomepage.join') }
-
-
+ {
+ this._renderJoinButton()
+ }
{
this._renderLegalese()
@@ -93,6 +92,50 @@ class WelcomePage extends AbstractWelcomePage {
);
}
+ /**
+ * Renders the join button.
+ *
+ * @private
+ * @returns {ReactElement}
+ */
+ _renderJoinButton() {
+ let children;
+
+ /* eslint-disable no-extra-parens */
+
+ if (this.state.joining) {
+ // TouchableHighlight is picky about what its children can be, so
+ // wrap it in a native component, i.e. View to avoid having to
+ // modify non-native children.
+ children = (
+
+
+
+ );
+ } else {
+ children = (
+
+ { this.props.t('welcomepage.join') }
+
+ );
+ }
+
+ /* eslint-enable no-extra-parens */
+
+ return (
+
+ {
+ children
+ }
+
+ );
+ }
+
/**
* Renders legal-related content such as Terms of service/use, Privacy
* policy, etc.
diff --git a/react/features/welcome/components/WelcomePage.web.js b/react/features/welcome/components/WelcomePage.web.js
index 75a748d21..a1890c8d8 100644
--- a/react/features/welcome/components/WelcomePage.web.js
+++ b/react/features/welcome/components/WelcomePage.web.js
@@ -38,7 +38,8 @@ class WelcomePage extends AbstractWelcomePage {
}
/**
- * This method is executed when comonent is mounted.
+ * Implements React's {@link Component#componentDidMount()}. Invoked
+ * immediately after this component is mounted.
*
* @inheritdoc
* @returns {void}