From a045353e6e74e6c1497c6ba0a030ec38fa88c769 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:09:50 -0700 Subject: [PATCH 01/24] ref(tooltbox): use componentDidUpdate to trigger more changes --- react/features/toolbox/components/web/Toolbox.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/react/features/toolbox/components/web/Toolbox.js b/react/features/toolbox/components/web/Toolbox.js index 88890386e6..026fd18df8 100644 --- a/react/features/toolbox/components/web/Toolbox.js +++ b/react/features/toolbox/components/web/Toolbox.js @@ -280,15 +280,15 @@ class Toolbox extends Component { * * @inheritdoc */ - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps) { // Ensure the dialog is closed when the toolbox becomes hidden. - if (this.props._overflowMenuVisible && !nextProps._visible) { + if (prevProps._overflowMenuVisible && !this.props._visible) { this._onSetOverflowVisible(false); } - if (this.props._overflowMenuVisible - && !this.props._dialog - && nextProps._dialog) { + if (prevProps._overflowMenuVisible + && !prevProps._dialog + && this.props._dialog) { this._onSetOverflowVisible(false); this.props.dispatch(setToolbarHovered(false)); } From b24e7ec5f003e68110b97ee51edbc21f6b1ecb4e Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:10:33 -0700 Subject: [PATCH 02/24] ref(labels): use getDerivedStateFromProps to get display state --- .../large-video/components/Labels.web.js | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/react/features/large-video/components/Labels.web.js b/react/features/large-video/components/Labels.web.js index 703e1a513c..930337c619 100644 --- a/react/features/large-video/components/Labels.web.js +++ b/react/features/large-video/components/Labels.web.js @@ -32,6 +32,19 @@ type State = { * @extends Component */ class Labels extends AbstractLabels { + /** + * Updates the state for whether or not the filmstrip is transitioning to + * a displayed state. + * + * @inheritdoc + */ + static getDerivedStateFromProps(props, prevState) { + return { + filmstripBecomingVisible: !prevState.filmstripBecomingVisible + && props._filmstripVisible + }; + } + /** * Initializes a new {@code Labels} instance. * @@ -46,22 +59,6 @@ class Labels extends AbstractLabels { }; } - /** - * Updates the state for whether or not the filmstrip is being toggled to - * display after having being hidden. - * - * @inheritdoc - * @param {Object} nextProps - The read-only props which this Component will - * receive. - * @returns {void} - */ - componentWillReceiveProps(nextProps) { - this.setState({ - filmstripBecomingVisible: nextProps._filmstripVisible - && !this.props._filmstripVisible - }); - } - /** * Implements React's {@link Component#render()}. * From 85f487cca59a7b1dde49068acd0ef63248615eae Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:10:39 -0700 Subject: [PATCH 03/24] ref(large-video): use componentDidUpdate to change background image --- .../large-video/components/LargeVideoBackground.web.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/react/features/large-video/components/LargeVideoBackground.web.js b/react/features/large-video/components/LargeVideoBackground.web.js index ee502666f7..d9f4c733f3 100644 --- a/react/features/large-video/components/LargeVideoBackground.web.js +++ b/react/features/large-video/components/LargeVideoBackground.web.js @@ -117,17 +117,15 @@ export class LargeVideoBackground extends Component { * Starts or stops the interval to update the image displayed in the canvas. * * @inheritdoc - * @param {Object} nextProps - The read-only React {@code Component} props - * with which the new instance is to be initialized. */ - componentWillReceiveProps(nextProps: Props) { - if (this.props.hidden && !nextProps.hidden) { + componentDidUpdate(prevProps: Props) { + if (prevProps.hidden && !this.props.hidden) { this._clearCanvas(); this._setUpdateCanvasInterval(); } - if ((!this.props.hidden && nextProps.hidden) - || !nextProps.videoElement) { + if ((!prevProps.hidden && this.props.hidden) + || !this.props.videoElement) { this._clearCanvas(); this._clearUpdateCanvasInterval(); } From 007d60eb6cc838fd9e8053935410feb535cfac8e Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:10:43 -0700 Subject: [PATCH 04/24] ref(toolbox): getter for the recording/streaming disabled tooltip --- .../base/toolbox/components/AbstractButton.js | 12 +++++++- .../LiveStream/web/LiveStreamButton.js | 30 +++++++------------ .../components/Recording/web/RecordButton.js | 20 ++++--------- 3 files changed, 26 insertions(+), 36 deletions(-) diff --git a/react/features/base/toolbox/components/AbstractButton.js b/react/features/base/toolbox/components/AbstractButton.js index de6f1c0e93..f3364ca5d1 100644 --- a/react/features/base/toolbox/components/AbstractButton.js +++ b/react/features/base/toolbox/components/AbstractButton.js @@ -206,6 +206,16 @@ export default class AbstractButton extends Component { return buttonStyles; } + /** + * Get the tooltip to display when hovering over the button. + * + * @private + * @returns {string} + */ + _getTooltip() { + return this.tooltip || ''; + } + /** * Helper function to be implemented by subclasses, which must return a * boolean value indicating if this button is disabled or not. @@ -258,7 +268,7 @@ export default class AbstractButton extends Component { iconName: this._getIconName(), label: this._getLabel(), styles: this._getStyles(), - tooltip: this.tooltip + tooltip: this._getTooltip() }; return ( diff --git a/react/features/recording/components/LiveStream/web/LiveStreamButton.js b/react/features/recording/components/LiveStream/web/LiveStreamButton.js index 3921440db3..9345160c06 100644 --- a/react/features/recording/components/LiveStream/web/LiveStreamButton.js +++ b/react/features/recording/components/LiveStream/web/LiveStreamButton.js @@ -37,26 +37,6 @@ class LiveStreamButton extends AbstractLiveStreamButton { iconName = 'icon-public'; toggledIconName = 'icon-public'; - /** - * Constructor of the component. - * - * @inheritdoc - */ - constructor(props: Props) { - super(props); - - this.tooltip = props._liveStreamDisabledTooltipKey; - } - - /** - * Implements {@code Component}'s componentWillReceiveProps. - * - * @inheritdoc - */ - componentWillReceiveProps(newProps: Props) { - this.tooltip = newProps._liveStreamDisabledTooltipKey; - } - /** * Helper function to be implemented by subclasses, which returns * a React Element to display (a beta tag) at the end of the button. @@ -76,6 +56,16 @@ class LiveStreamButton extends AbstractLiveStreamButton { ); } + /** + * Returns the tooltip that should be displayed when the button is disabled. + * + * @private + * @returns {string} + */ + _getTooltip() { + return this.props._liveStreamDisabledTooltipKey || ''; + } + /** * Helper function to be implemented by subclasses, which must return a * boolean value indicating if this button is disabled or not. diff --git a/react/features/recording/components/Recording/web/RecordButton.js b/react/features/recording/components/Recording/web/RecordButton.js index 8abc848f1e..d3f919bdfd 100644 --- a/react/features/recording/components/Recording/web/RecordButton.js +++ b/react/features/recording/components/Recording/web/RecordButton.js @@ -36,23 +36,13 @@ class RecordButton extends AbstractRecordButton { toggledIconName = 'icon-camera-take-picture'; /** - * Constructor of the component. + * Returns the tooltip that should be displayed when the button is disabled. * - * @inheritdoc + * @private + * @returns {string} */ - constructor(props: Props) { - super(props); - - this.tooltip = props._fileRecordingsDisabledTooltipKey; - } - - /** - * Implements {@code Component}'s componentWillReceiveProps. - * - * @inheritdoc - */ - componentWillReceiveProps(newProps: Props) { - this.tooltip = newProps._fileRecordingsDisabledTooltipKey; + _getTooltip() { + return this.tooltip || ''; } /** From 1e3e71c2ffccecac38b50839a8bb454b3c63392c Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:10:47 -0700 Subject: [PATCH 05/24] ref(speaker-stats): begin polling for stats after mount --- .../speaker-stats/components/SpeakerStats.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/react/features/speaker-stats/components/SpeakerStats.js b/react/features/speaker-stats/components/SpeakerStats.js index c191f56cef..56f565929a 100644 --- a/react/features/speaker-stats/components/SpeakerStats.js +++ b/react/features/speaker-stats/components/SpeakerStats.js @@ -12,7 +12,7 @@ import SpeakerStatsLabels from './SpeakerStatsLabels'; declare var interfaceConfig: Object; /** - * The type of the React {@code Component} props of {@link SpeakerStats} + * The type of the React {@code Component} props of {@link SpeakerStats}. */ type Props = { @@ -33,7 +33,7 @@ type Props = { }; /** - * The type of the React {@code Component} state of {@link SpeakerStats} + * The type of the React {@code Component} state of {@link SpeakerStats}. */ type State = { @@ -49,10 +49,6 @@ type State = { * @extends Component */ class SpeakerStats extends Component { - state = { - stats: {} - }; - _updateInterval: IntervalID; /** @@ -64,19 +60,20 @@ class SpeakerStats extends Component { constructor(props) { super(props); + this.state = { + stats: this.props.conference.getSpeakerStats() + }; + // Bind event handlers so they are only bound once per instance. this._updateStats = this._updateStats.bind(this); } /** - * Immediately request for updated speaker stats and begin - * polling for speaker stats updates. + * Begin polling for speaker stats updates. * * @inheritdoc - * @returns {void} */ - componentWillMount() { - this._updateStats(); + componentDidMount() { this._updateInterval = setInterval(this._updateStats, 1000); } From e9b2518f8adcca033cc8c6d5f689d2a51b74d5f9 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:10:51 -0700 Subject: [PATCH 06/24] ref(info): use getDerivedStateFromProps to update state --- .../components/info-dialog/InfoDialog.web.js | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/react/features/invite/components/info-dialog/InfoDialog.web.js b/react/features/invite/components/info-dialog/InfoDialog.web.js index 9ae652c46c..dbb7b1fcbb 100644 --- a/react/features/invite/components/info-dialog/InfoDialog.web.js +++ b/react/features/invite/components/info-dialog/InfoDialog.web.js @@ -112,6 +112,28 @@ type State = { class InfoDialog extends Component { _copyElement: ?Object; + /** + * Implements React's {@link Component#getDerivedStateFromProps()}. + * + * @inheritdoc + */ + static getDerivedStateFromProps(props, state) { + let phoneNumber = state.phoneNumber; + + if (!state.phoneNumber && props.dialIn.numbers) { + const { defaultCountry, numbers } = props.dialIn; + + phoneNumber = _getDefaultPhoneNumber(numbers, defaultCountry); + } + + return { + // Exit edit mode when a password is set locally or remotely. + passwordEditEnabled: state.passwordEditEnabled && props._password + ? false : state.passwordEditEnabled, + phoneNumber + }; + } + /** * {@code InfoDialog} component's local state. * @@ -162,28 +184,6 @@ class InfoDialog extends Component { this._setCopyElement = this._setCopyElement.bind(this); } - /** - * Implements React's {@link Component#componentWillReceiveProps()}. Invoked - * before this mounted component receives new props. - * - * @inheritdoc - * @param {Props} nextProps - New props component will receive. - */ - componentWillReceiveProps(nextProps) { - if (!this.props._password && nextProps._password) { - this.setState({ passwordEditEnabled: false }); - } - - if (!this.state.phoneNumber && nextProps.dialIn.numbers) { - const { defaultCountry, numbers } = nextProps.dialIn; - - this.setState({ - phoneNumber: - _getDefaultPhoneNumber(numbers, defaultCountry) - }); - } - } - /** * Implements React's {@link Component#render()}. * From 280178f5d19df9e070f3bed7669dd51e2898ce39 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:10:55 -0700 Subject: [PATCH 07/24] ref(info-dialog): derive when to autoshow or autohide --- .../invite/components/InfoDialogButton.web.js | 58 ++++++++----------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/react/features/invite/components/InfoDialogButton.web.js b/react/features/invite/components/InfoDialogButton.web.js index 06e2cccb0c..916232b05d 100644 --- a/react/features/invite/components/InfoDialogButton.web.js +++ b/react/features/invite/components/InfoDialogButton.web.js @@ -70,6 +70,12 @@ type Props = { */ type State = { + /** + * Cache the conference connection state to derive when transitioning from + * not joined to join, in order to auto-show the InfoDialog. + */ + hasConnectedToConference: boolean, + /** * Whether or not {@code InfoDialog} should be visible. */ @@ -83,6 +89,23 @@ type State = { * @extends Component */ class InfoDialogButton extends Component { + /** + * Implements React's {@link Component#getDerivedStateFromProps()}. + * + * @inheritdoc + */ + static getDerivedStateFromProps(props, state) { + return { + hasConnectedToConference: props._isConferenceJoined, + showDialog: (props._toolboxVisible && state.showDialog) + || (!state.hasConnectedToConference + && props._isConferenceJoined + && props._participantCount < 2 + && props._toolboxVisible + && !props._disableAutoShow) + }; + } + /** * Initializes new {@code InfoDialogButton} instance. * @@ -92,6 +115,7 @@ class InfoDialogButton extends Component { super(props); this.state = { + hasConnectedToConference: props._isConferenceJoined, showDialog: false }; @@ -111,22 +135,6 @@ class InfoDialogButton extends Component { } } - /** - * Update the visibility of the {@code InfoDialog}. - * - * @inheritdoc - */ - componentWillReceiveProps(nextProps) { - // Ensure the dialog is closed when the toolbox becomes hidden. - if (this.state.showDialog && !nextProps._toolboxVisible) { - this._onDialogClose(); - - return; - } - - this._maybeAutoShowDialog(nextProps); - } - /** * Implements React's {@link Component#render()}. * @@ -159,24 +167,6 @@ class InfoDialogButton extends Component { ); } - /** - * Invoked to trigger display of the {@code InfoDialog} if certain - * conditions are met. - * - * @param {Object} nextProps - The future properties of this component. - * @private - * @returns {void} - */ - _maybeAutoShowDialog(nextProps) { - if (!this.props._isConferenceJoined - && nextProps._isConferenceJoined - && nextProps._participantCount < 2 - && nextProps._toolboxVisible - && !nextProps._disableAutoShow) { - this.setState({ showDialog: true }); - } - } - _onDialogClose: () => void; /** From c28c70fb2ff6a9307fa177933d08c9bd9cae236c Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:10:58 -0700 Subject: [PATCH 08/24] ref(device-selection): change audio preview listener on component update --- .../device-selection/components/AudioInputPreview.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/react/features/device-selection/components/AudioInputPreview.js b/react/features/device-selection/components/AudioInputPreview.js index 987d38995a..2953568c46 100644 --- a/react/features/device-selection/components/AudioInputPreview.js +++ b/react/features/device-selection/components/AudioInputPreview.js @@ -63,9 +63,9 @@ class AudioInputPreview extends Component { * @inheritdoc * @returns {void} */ - componentWillReceiveProps(nextProps: Props) { - if (nextProps.track !== this.props.track) { - this._listenForAudioUpdates(nextProps.track); + componentDidUpdate(prevProps: Props) { + if (prevProps.track !== this.props.track) { + this._listenForAudioUpdates(this.props.track); this._updateAudioLevel(0); } } From e0cbb838be178eaab3f1ddc0365bf5f397c12dcb Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:11:03 -0700 Subject: [PATCH 09/24] ref(info): derive when to clear the entered password state --- .../info-dialog/PasswordForm.web.js | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/react/features/invite/components/info-dialog/PasswordForm.web.js b/react/features/invite/components/info-dialog/PasswordForm.web.js index e5115271d9..e75faea57e 100644 --- a/react/features/invite/components/info-dialog/PasswordForm.web.js +++ b/react/features/invite/components/info-dialog/PasswordForm.web.js @@ -55,6 +55,17 @@ type State = { * @extends Component */ class PasswordForm extends Component { + /** + * Implements React's {@link Component#getDerivedStateFromProps()}. + * + * @inheritdoc + */ + static getDerivedStateFromProps(props, state) { + return { + enteredPassword: props.editEnabled ? state.enteredPassword : '' + }; + } + state = { enteredPassword: '' }; @@ -74,19 +85,6 @@ class PasswordForm extends Component { this._onPasswordSubmit = this._onPasswordSubmit.bind(this); } - /** - * Implements React's {@link Component#componentWillReceiveProps()}. Invoked - * before this mounted component receives new props. - * - * @inheritdoc - * @param {Props} nextProps - New props component will receive. - */ - componentWillReceiveProps(nextProps: Props) { - if (this.props.editEnabled && !nextProps.editEnabled) { - this.setState({ enteredPassword: '' }); - } - } - /** * Implements React's {@link Component#render()}. * @@ -182,5 +180,4 @@ class PasswordForm extends Component { } } - export default translate(PasswordForm); From 45068f68db798e378f5759c74aec90cd84c12972 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Sun, 28 Oct 2018 19:11:10 -0700 Subject: [PATCH 10/24] ref(welcome-page): use getDerivedStateFromProps, set mounted after actual mount --- .../welcome/components/AbstractWelcomePage.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/react/features/welcome/components/AbstractWelcomePage.js b/react/features/welcome/components/AbstractWelcomePage.js index b67ca3f38f..02d089ca23 100644 --- a/react/features/welcome/components/AbstractWelcomePage.js +++ b/react/features/welcome/components/AbstractWelcomePage.js @@ -37,6 +37,17 @@ type Props = { export class AbstractWelcomePage extends Component { _mounted: ?boolean; + /** + * Implements React's {@link Component#getDerivedStateFromProps()}. + * + * @inheritdoc + */ + static getDerivedStateFromProps(props: Props, state: Object) { + return { + room: props._room || state.room + }; + } + /** * Save room name into component's local state. * @@ -77,26 +88,15 @@ export class AbstractWelcomePage extends Component { } /** - * Implements React's {@link Component#componentWillMount()}. Invoked - * immediately before mounting occurs. + * Implements React's {@link Component#componentDidMount()}. Invoked + * immediately after mounting occurs. * * @inheritdoc */ - componentWillMount() { + componentDidMount() { this._mounted = true; } - /** - * Implements React's {@link Component#componentWillReceiveProps()}. Invoked - * before this mounted component receives new props. - * - * @inheritdoc - * @param {Props} nextProps - New props component will receive. - */ - componentWillReceiveProps(nextProps: Props) { - this.setState({ room: nextProps._room }); - } - /** * Implements React's {@link Component#componentWillUnmount()}. Invoked * immediately before this component is unmounted and destroyed. From 72c1fa38be3af8a22abd6f50f3836dfbe646a7fb Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 29 Oct 2018 09:30:31 -0700 Subject: [PATCH 11/24] ref(modal): simplify functional footer passing to remove componentWillUpdate --- .../dialog/components/StatelessDialog.web.js | 99 ++++++------------- 1 file changed, 32 insertions(+), 67 deletions(-) diff --git a/react/features/base/dialog/components/StatelessDialog.web.js b/react/features/base/dialog/components/StatelessDialog.web.js index f00be75195..6db62dd2df 100644 --- a/react/features/base/dialog/components/StatelessDialog.web.js +++ b/react/features/base/dialog/components/StatelessDialog.web.js @@ -102,26 +102,8 @@ class StatelessDialog extends Component { this._onDialogDismissed = this._onDialogDismissed.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onSubmit = this._onSubmit.bind(this); + this._renderFooter = this._renderFooter.bind(this); this._setDialogElement = this._setDialogElement.bind(this); - - this._Footer = this._createFooterConstructor(props); - } - - /** - * React Component method that executes before the component is updated. - * - * @inheritdoc - * @param {Object} nextProps - The next properties, before the update. - * @returns {void} - */ - componentWillUpdate(nextProps) { - // If button states have changed, update the Footer constructor function - // so buttons of the proper state are rendered. - if (nextProps.okDisabled !== this.props.okDisabled - || nextProps.cancelDisabled !== this.props.cancelDisabled - || nextProps.submitDisabled !== this.props.submitDisabled) { - this._Footer = this._createFooterConstructor(nextProps); - } } /** @@ -142,7 +124,7 @@ class StatelessDialog extends Component { return ( { ); } - _onCancel: () => Function; + _renderFooter: () => React$Node; /** - * Returns a functional component to be used for the modal footer. + * Returns a ReactElement to display buttons for closing the modal. * - * @param {Object} options - The configuration for how the buttons in the - * footer should display. Essentially {@code StatelessDialog} props should - * be passed in. + * @param {Object} propsFromModalFooter - The props passed in from the + * {@link ModalFooter} component. * @private * @returns {ReactElement} */ - _createFooterConstructor(options) { + _renderFooter(propsFromModalFooter) { // Filter out falsy (null) values because {@code ButtonGroup} will error // if passed in anything but buttons with valid type props. const buttons = [ - this._renderOKButton(options), - this._renderCancelButton(options) + this._renderOKButton(), + this._renderCancelButton() ].filter(Boolean); - return function Footer(modalFooterProps) { - return ( - - { - - /** - * Atlaskit has this empty span (JustifySim) so... - */ - } - - - { buttons } - - - ); - }; + return ( + + { + + /** + * Atlaskit has this empty span (JustifySim) so... + */ + } + + + { buttons } + + + ); } _onCancel: () => void; @@ -257,21 +236,14 @@ class StatelessDialog extends Component { /** * Renders Cancel button. * - * @param {Object} options - The configuration for the Cancel button. - * @param {boolean} options.cancelDisabled - True if the cancel button - * should not be rendered. - * @param {string} options.cancelTitleKey - The translation key to use as - * text on the button. - * @param {boolean} options.isModal - True if the cancel button should not - * be rendered. * @private * @returns {ReactElement|null} The Cancel button if enabled and dialog is * not modal. */ - _renderCancelButton(options = {}) { - if (options.cancelDisabled - || options.isModal - || options.hideCancelButton) { + _renderCancelButton() { + if (this.props.cancelDisabled + || this.props.isModal + || this.props.hideCancelButton) { return null; } @@ -286,7 +258,7 @@ class StatelessDialog extends Component { key = 'cancel' onClick = { this._onCancel } type = 'button'> - { t(options.cancelTitleKey || 'dialog.Cancel') } + { t(this.props.cancelTitleKey || 'dialog.Cancel') } ); } @@ -294,18 +266,11 @@ class StatelessDialog extends Component { /** * Renders OK button. * - * @param {Object} options - The configuration for the OK button. - * @param {boolean} options.okDisabled - True if the button should display - * as disabled and clicking should have no effect. - * @param {string} options.okTitleKey - The translation key to use as text - * on the button. - * @param {boolean} options.submitDisabled - True if the button should not - * be rendered. * @private * @returns {ReactElement|null} The OK button if enabled. */ - _renderOKButton(options = {}) { - if (options.submitDisabled) { + _renderOKButton() { + if (this.props.submitDisabled) { return null; } @@ -318,11 +283,11 @@ class StatelessDialog extends Component { appearance = 'primary' form = 'modal-dialog-form' id = { OK_BUTTON_ID } - isDisabled = { options.okDisabled } + isDisabled = { this.props.okDisabled } key = 'submit' onClick = { this._onSubmit } type = 'button'> - { t(options.okTitleKey || 'dialog.Ok') } + { t(this.props.okTitleKey || 'dialog.Ok') } ); } From eaafc21133999214f0f4e2682580b87a42483936 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 29 Oct 2018 10:21:33 -0700 Subject: [PATCH 12/24] ref(desktop-picker): derive desired types when props change --- .../components/DesktopPicker.js | 66 ++++++++----------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/react/features/desktop-picker/components/DesktopPicker.js b/react/features/desktop-picker/components/DesktopPicker.js index bbb6e22252..6f190a9743 100644 --- a/react/features/desktop-picker/components/DesktopPicker.js +++ b/react/features/desktop-picker/components/DesktopPicker.js @@ -1,7 +1,7 @@ // @flow import Tabs from '@atlaskit/tabs'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import { Dialog, hideDialog } from '../../base/dialog'; @@ -94,12 +94,36 @@ type State = { types: Array }; + /** * React component for DesktopPicker. * * @extends Component */ -class DesktopPicker extends Component { +class DesktopPicker extends PureComponent { + /** + * Implements React's {@link Component#getDerivedStateFromProps()}. + * + * @inheritdoc + */ + static getDerivedStateFromProps(props) { + return { + types: DesktopPicker._getValidTypes(props.desktopSharingSources) + }; + } + + /** + * Extracts only the valid types from the passed {@code types}. + * + * @param {Array} types - The types to filter. + * @private + * @returns {Array} The filtered types. + */ + static _getValidTypes(types = []) { + return types.filter( + type => VALID_TYPES.includes(type)); + } + _poller = null; state = { @@ -133,7 +157,7 @@ class DesktopPicker extends Component { this._updateSources = this._updateSources.bind(this); this.state.types - = this._getValidTypes(this.props.desktopSharingSources); + = DesktopPicker._getValidTypes(this.props.desktopSharingSources); } /** @@ -146,31 +170,6 @@ class DesktopPicker extends Component { this._startPolling(); } - /** - * Notifies this mounted React Component that it will receive new props. - * Sets a default selected source if one is not already set. - * - * @inheritdoc - * @param {Object} nextProps - The read-only React Component props that this - * instance will receive. - * @returns {void} - */ - componentWillReceiveProps(nextProps: Props) { - const { desktopSharingSources } = nextProps; - - /** - * Do only reference check in order to not calculate the types on every - * update. This is enough for our use case and we don't need value - * checking because if the value is the same we won't change the - * reference for the desktopSharingSources array. - */ - if (desktopSharingSources !== this.props.desktopSharingSources) { - this.setState({ - types: this._getValidTypes(desktopSharingSources) - }); - } - } - /** * Clean up component and DesktopCapturerSource store state. * @@ -241,17 +240,6 @@ class DesktopPicker extends Component { return selectedSource; } - /** - * Extracts only the valid types from the passed {@code types}. - * - * @param {Array} types - The types to filter. - * @returns {Array} The filtered types. - */ - _getValidTypes(types = []) { - return types.filter( - type => VALID_TYPES.includes(type)); - } - _onCloseModal: (?string, string) => void; /** From c288d0e18c3799432a738221ddcfbd1a8651b204 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 29 Oct 2018 11:16:44 -0700 Subject: [PATCH 13/24] ref(deep-linking): set initial state in constructor --- .../components/DeepLinkingMobilePage.web.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/react/features/deep-linking/components/DeepLinkingMobilePage.web.js b/react/features/deep-linking/components/DeepLinkingMobilePage.web.js index 230c0484af..a5f117147e 100644 --- a/react/features/deep-linking/components/DeepLinkingMobilePage.web.js +++ b/react/features/deep-linking/components/DeepLinkingMobilePage.web.js @@ -81,23 +81,15 @@ class DeepLinkingMobilePage extends Component { constructor(props: Props) { super(props); + this.state = { + joinURL: generateDeepLinkingURL() + }; + // Bind event handlers so they are only bound once per instance. this._onDownloadApp = this._onDownloadApp.bind(this); this._onOpenApp = this._onOpenApp.bind(this); } - /** - * Initializes the text and URL of the `Start a conference` / `Join the - * conversation` button which takes the user to the mobile app. - * - * @inheritdoc - */ - componentWillMount() { - this.setState({ - joinURL: generateDeepLinkingURL() - }); - } - /** * Implements the Component's componentDidMount method. * From 14adc0b887c2d7465a2209178b3ca94c680126b8 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 29 Oct 2018 11:48:18 -0700 Subject: [PATCH 14/24] ref(always-on-top): trigger toolbar hide timeout after update --- react/features/always-on-top/AlwaysOnTop.js | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/react/features/always-on-top/AlwaysOnTop.js b/react/features/always-on-top/AlwaysOnTop.js index 7e66009fed..dffccbcf90 100644 --- a/react/features/always-on-top/AlwaysOnTop.js +++ b/react/features/always-on-top/AlwaysOnTop.js @@ -205,6 +205,18 @@ export default class AlwaysOnTop extends Component<*, State> { this._hideToolbarAfterTimeout(); } + /** + * Sets a timeout to hide the toolbar when the toolbar is shown. + * + * @inheritdoc + * @returns {void} + */ + componentDidUpdate(prevProps: *, prevState: State) { + if (!prevState.visible && this.state.visible) { + this._hideToolbarAfterTimeout(); + } + } + /** * Removes all listeners. * @@ -223,18 +235,6 @@ export default class AlwaysOnTop extends Component<*, State> { window.removeEventListener('mousemove', this._mouseMove); } - /** - * Sets a timeout to hide the toolbar when the toolbar is shown. - * - * @inheritdoc - * @returns {void} - */ - componentWillUpdate(nextProps: *, nextState: State) { - if (!this.state.visible && nextState.visible) { - this._hideToolbarAfterTimeout(); - } - } - /** * Implements React's {@link Component#render()}. * From 77f8f85b965ca19a71110ecba255fed65308416c Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 29 Oct 2018 12:10:10 -0700 Subject: [PATCH 15/24] ref(device-selection): update preview tracks on component update --- .../components/DeviceSelection.js | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/react/features/device-selection/components/DeviceSelection.js b/react/features/device-selection/components/DeviceSelection.js index 2344896d4d..ec5f034397 100644 --- a/react/features/device-selection/components/DeviceSelection.js +++ b/react/features/device-selection/components/DeviceSelection.js @@ -151,7 +151,8 @@ class DeviceSelection extends AbstractDialogTab { } /** - * Checks if audio / video permissions were granted. + * Checks if audio / video permissions were granted. Updates audio input and + * video input previews. * * @param {Object} prevProps - Previous props this component received. * @param {Object} prevState - Previous state this component had. @@ -174,25 +175,15 @@ class DeviceSelection extends AbstractDialogTab { }); }); } - } - - /** - * Updates audio input and video input previews. - * - * @inheritdoc - * @param {Object} nextProps - The read-only props which this Component will - * receive. - * @returns {void} - */ - componentWillReceiveProps(nextProps: Object) { - const { selectedAudioInputId, selectedVideoInputId } = this.props; - if (selectedAudioInputId !== nextProps.selectedAudioInputId) { - this._createAudioInputTrack(nextProps.selectedAudioInputId); + if (prevProps.selectedAudioInputId + !== this.props.selectedAudioInputId) { + this._createAudioInputTrack(this.props.selectedAudioInputId); } - if (selectedVideoInputId !== nextProps.selectedVideoInputId) { - this._createVideoInputTrack(nextProps.selectedVideoInputId); + if (prevProps.selectedVideoInputId + !== this.props.selectedVideoInputId) { + this._createVideoInputTrack(this.props.selectedVideoInputId); } } From 609f3887f2cb8cdf87f9b2434087fb8523cb3e1a Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 29 Oct 2018 12:27:12 -0700 Subject: [PATCH 16/24] ref(welcome-page): native creates/destroys camera after mount --- react/features/welcome/components/WelcomePage.native.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js index b9afe22507..ae9b3f1cf2 100644 --- a/react/features/welcome/components/WelcomePage.native.js +++ b/react/features/welcome/components/WelcomePage.native.js @@ -57,15 +57,15 @@ class WelcomePage extends AbstractWelcomePage { } /** - * Implements React's {@link Component#componentWillMount()}. Invoked - * immediately before mounting occurs. Creates a local video track if none + * Implements React's {@link Component#componentDidMount()}. Invoked + * immediately after mounting occurs. Creates a local video track if none * is available and the camera permission was already granted. * * @inheritdoc * @returns {void} */ - componentWillMount() { - super.componentWillMount(); + componentDidMount() { + super.componentDidMount(); const { dispatch } = this.props; From c4f1588bb0e2b2d55b8db0d370248a146c984ca0 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 29 Oct 2018 14:40:14 -0700 Subject: [PATCH 17/24] ref(dialog): set mounted flag after mount --- react/features/base/dialog/components/AbstractDialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react/features/base/dialog/components/AbstractDialog.js b/react/features/base/dialog/components/AbstractDialog.js index 3b32ea2570..2a30bcf782 100644 --- a/react/features/base/dialog/components/AbstractDialog.js +++ b/react/features/base/dialog/components/AbstractDialog.js @@ -49,12 +49,12 @@ export default class AbstractDialog

} /** - * Implements React's {@link Component#componentWillMount()}. Invoked + * Implements React's {@link Component#componentDidMount()}. Invoked * immediately before mounting occurs. * * @inheritdoc */ - componentWillMount() { + componentDidMount() { this._mounted = true; } From d5fb2c271771e9440d18c5534342fd93a24c8521 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 29 Oct 2018 15:06:23 -0700 Subject: [PATCH 18/24] ref(sdk): update comments to exclude mention of componentWillReceiveProps --- android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java | 2 +- ios/sdk/src/JitsiMeetView.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java index da973d1581..f60ecb8644 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java @@ -262,7 +262,7 @@ public class JitsiMeetView // the respective conference again if the first invocation was followed // by leaving the conference. However, React and, respectively, // appProperties/initialProperties are declarative expressions i.e. one - // and the same URL will not trigger componentWillReceiveProps in the + // and the same URL will not trigger an automatic re-render in the // JavaScript source code. The workaround implemented bellow introduces // imperativeness in React Component props by defining a unique value // per loadURLObject: invocation. diff --git a/ios/sdk/src/JitsiMeetView.m b/ios/sdk/src/JitsiMeetView.m index 0799ef0cbe..658d44585d 100644 --- a/ios/sdk/src/JitsiMeetView.m +++ b/ios/sdk/src/JitsiMeetView.m @@ -268,7 +268,7 @@ static NSMapTable *views; // conference again if the first invocation was followed by leaving the // conference. However, React and, respectively, // appProperties/initialProperties are declarative expressions i.e. one and - // the same URL will not trigger componentWillReceiveProps in the JavaScript + // the same URL will not trigger an automatic re-render in the JavaScript // source code. The workaround implemented bellow introduces imperativeness // in React Component props by defining a unique value per loadURLObject: // invocation. From 3a32f7f3f0d7128fa5b802e93bcad253c718ff77 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Thu, 1 Nov 2018 10:32:11 -0700 Subject: [PATCH 19/24] ref(audio-picker): fetch audio devices after mount Per react migration docs, initially fetching external data is recommended to be done in componentDidMount. --- .../mobile/audio-mode/components/AudioRoutePickerDialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js b/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js index 5ce1ad4557..6f20ffce3f 100644 --- a/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js +++ b/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js @@ -109,7 +109,7 @@ class AudioRoutePickerDialog extends Component { state = { /** * Available audio devices, it will be set in - * {@link #componentWillMount()}. + * {@link #componentDidMount()}. */ devices: [] }; @@ -132,7 +132,7 @@ class AudioRoutePickerDialog extends Component { * * @inheritdoc */ - componentWillMount() { + componentDidMount() { AudioMode.getAudioDevices().then(({ devices, selected }) => { const audioDevices = []; From d6216f21d5c2de3411c90a95bfacd7b9b7fb322d Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Thu, 1 Nov 2018 16:20:40 -0700 Subject: [PATCH 20/24] ref(live-streaming): remove picker state to remove componentWillReceiveProps --- .../LiveStream/AbstractStreamKeyForm.js | 31 +------------------ .../LiveStream/native/StreamKeyForm.js | 2 +- .../LiveStream/web/StreamKeyForm.js | 2 +- 3 files changed, 3 insertions(+), 32 deletions(-) diff --git a/react/features/recording/components/LiveStream/AbstractStreamKeyForm.js b/react/features/recording/components/LiveStream/AbstractStreamKeyForm.js index 0a393267e8..278a03c74e 100644 --- a/react/features/recording/components/LiveStream/AbstractStreamKeyForm.js +++ b/react/features/recording/components/LiveStream/AbstractStreamKeyForm.js @@ -33,24 +33,13 @@ export type Props = { value: string }; -/** - * The state of the component. - */ -type State = { - - /** - * The value entered in the field. - */ - value: string -} - /** * An abstract React Component for entering a key for starting a YouTube live * stream. * * @extends Component */ -export default class AbstractStreamKeyForm extends Component { +export default class AbstractStreamKeyForm extends Component { helpURL: string; /** @@ -61,10 +50,6 @@ export default class AbstractStreamKeyForm extends Component { constructor(props: Props) { super(props); - this.state = { - value: props.value - }; - this.helpURL = (typeof interfaceConfig !== 'undefined' && interfaceConfig.LIVE_STREAMING_HELP_LINK) || LIVE_STREAMING_HELP_LINK; @@ -73,17 +58,6 @@ export default class AbstractStreamKeyForm extends Component { this._onInputChange = this._onInputChange.bind(this); } - /** - * Implements {@code Component}'s componentWillReceiveProps. - * - * @inheritdoc - */ - componentWillReceiveProps(newProps: Props) { - this.setState({ - value: newProps.value - }); - } - _onInputChange: Object => void /** @@ -99,9 +73,6 @@ export default class AbstractStreamKeyForm extends Component { _onInputChange(change) { const value = typeof change === 'object' ? change.target.value : change; - this.setState({ - value - }); this.props.onChange(value); } } diff --git a/react/features/recording/components/LiveStream/native/StreamKeyForm.js b/react/features/recording/components/LiveStream/native/StreamKeyForm.js index 91606ff627..a97e9e52b2 100644 --- a/react/features/recording/components/LiveStream/native/StreamKeyForm.js +++ b/react/features/recording/components/LiveStream/native/StreamKeyForm.js @@ -50,7 +50,7 @@ class StreamKeyForm extends AbstractStreamKeyForm { onChangeText = { this._onInputChange } placeholder = { t('liveStreaming.enterStreamKey') } style = { styles.streamKeyInput } - value = { this.state.value } /> + value = { this.props.value } /> diff --git a/react/features/recording/components/LiveStream/web/StreamKeyForm.js b/react/features/recording/components/LiveStream/web/StreamKeyForm.js index 9ce96d6212..334029d003 100644 --- a/react/features/recording/components/LiveStream/web/StreamKeyForm.js +++ b/react/features/recording/components/LiveStream/web/StreamKeyForm.js @@ -51,7 +51,7 @@ class StreamKeyForm extends AbstractStreamKeyForm { placeholder = { t('liveStreaming.enterStreamKey') } shouldFitContainer = { true } type = 'text' - value = { this.state.value } /> + value = { this.props.value } /> { this.helpURL ?

Date: Fri, 2 Nov 2018 09:00:14 -0700 Subject: [PATCH 21/24] ref(blank-page): destroy local track after mount To kill componentWillMount, call destroyLocalTrack after mount. Navigation to the blank page was synthetically forced and no UI issues were noticed, possibly because destroyLocalTrack may already be async so destruction may already have been occurring after mount. --- react/features/welcome/components/BlankPage.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/features/welcome/components/BlankPage.native.js b/react/features/welcome/components/BlankPage.native.js index 60c5e9a0ef..8a71af59fe 100644 --- a/react/features/welcome/components/BlankPage.native.js +++ b/react/features/welcome/components/BlankPage.native.js @@ -29,7 +29,7 @@ class BlankPage extends Component { * @inheritdoc * @returns {void} */ - componentWillMount() { + componentDidMount() { this.props.dispatch(destroyLocalTracks()); } From 5cb4bec633bf6c29191fd80f214e59d46831491e Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Fri, 2 Nov 2018 14:48:03 -0700 Subject: [PATCH 22/24] ref(circular-label): animate after dom updates Based on react-native docs, looks like animations should be started after mount. Updating animation states I'm not certain on so I moved it to componentDidUpdate and tested with the live streaming label to ensure the component still animated fine. --- .../base/label/components/CircularLabel.native.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/react/features/base/label/components/CircularLabel.native.js b/react/features/base/label/components/CircularLabel.native.js index 864ac7694b..6b6c5cd281 100644 --- a/react/features/base/label/components/CircularLabel.native.js +++ b/react/features/base/label/components/CircularLabel.native.js @@ -64,17 +64,24 @@ export default class CircularLabel extends AbstractCircularLabel { this.state = { pulseAnimation: new Animated.Value(0) }; + } - this._maybeToggleAnimation({}, props); + /** + * Implements {@code Component#componentDidMount}. + * + * @inheritdoc + */ + componentDidMount() { + this._maybeToggleAnimation({}, this.props); } /** - * Implements {@code Component#componentWillReceiveProps}. + * Implements {@code Component#componentDidUpdate}. * * @inheritdoc */ - componentWillReceiveProps(newProps: Props) { - this._maybeToggleAnimation(this.props, newProps); + componentDidUpdate(prevProps: Props) { + this._maybeToggleAnimation(prevProps, this.props); } /** From f13cfe70f3b7b9ad825d4cc53c7f49b24d198454 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Fri, 2 Nov 2018 16:19:42 -0700 Subject: [PATCH 23/24] ref(sidebar): derive showOverlay state - Derive the showOverlay state. When the sidebar should be hidden, the internal showOverlay state should remain true until the animation hides it. When the sidebar should show, the showOverlay state should become true immediately. - Use PureComponent to prevent additional animation triggers instead of explicitly checking changes to the "show" prop. --- .../base/react/components/native/SideBar.js | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/react/features/base/react/components/native/SideBar.js b/react/features/base/react/components/native/SideBar.js index 7999fb6258..4086df6d2a 100644 --- a/react/features/base/react/components/native/SideBar.js +++ b/react/features/base/react/components/native/SideBar.js @@ -1,6 +1,6 @@ // @flow -import React, { Component, type Node } from 'react'; +import React, { PureComponent, type Node } from 'react'; import { Animated, TouchableWithoutFeedback, View } from 'react-native'; import styles, { SIDEBAR_WIDTH } from './styles'; @@ -46,7 +46,18 @@ type State = { /** * A generic animated side bar to be used for left-side, hamburger-style menus. */ -export default class SideBar extends Component { +export default class SideBar extends PureComponent { + /** + * Implements React's {@link Component#getDerivedStateFromProps()}. + * + * @inheritdoc + */ + static getDerivedStateFromProps(props: Props, prevState: State) { + return { + showOverlay: props.show || prevState.showOverlay + }; + } + /** * Initializes a new {@code SideBar} instance. * @@ -74,12 +85,12 @@ export default class SideBar extends Component { } /** - * Implements React's {@link Component#componentWillReceiveProps()}. + * Implements React's {@link Component#componentDidUpdate()}. * * @inheritdoc */ - componentWillReceiveProps({ show }: Props) { - (show === this.props.show) || this._setShow(show); + componentDidUpdate() { + this._setShow(this.props.show); } /** @@ -148,8 +159,6 @@ export default class SideBar extends Component { * @returns {void} */ _setShow(show) { - show && this.setState({ showOverlay: true }); - Animated .timing( /* value */ this.state.sliderAnimation, From d4e18e78faa1540a59d20cd87f828c2afe4b11e4 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 5 Nov 2018 14:14:44 -0800 Subject: [PATCH 24/24] ref(recording-label): derive when the label state is no longer stale --- .../components/AbstractRecordingLabel.js | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/react/features/recording/components/AbstractRecordingLabel.js b/react/features/recording/components/AbstractRecordingLabel.js index 78fcb5c342..227e58cf73 100644 --- a/react/features/recording/components/AbstractRecordingLabel.js +++ b/react/features/recording/components/AbstractRecordingLabel.js @@ -54,6 +54,18 @@ const STALE_TIMEOUT = 10 * 1000; */ export default class AbstractRecordingLabel extends Component { + /** + * Implements {@code Component#getDerivedStateFromProps}. + * + * @inheritdoc + */ + static getDerivedStateFromProps(props: Props, prevState: State) { + return { + staleLabel: props._status !== JitsiRecordingConstants.status.OFF + && prevState.staleLabel ? false : prevState.staleLabel + }; + } + /** * Initializes a new {@code AbstractRecordingLabel} component. * @@ -70,12 +82,12 @@ export default class AbstractRecordingLabel } /** - * Implements {@code Component#componentWillReceiveProps}. + * Implements {@code Component#componentDidUpdate}. * * @inheritdoc */ - componentWillReceiveProps(newProps: Props) { - this._updateStaleStatus(this.props, newProps); + componentDidUpdate(prevProps: Props) { + this._updateStaleStatus(prevProps, this.props); } /** @@ -137,13 +149,8 @@ export default class AbstractRecordingLabel } }, STALE_TIMEOUT); } - } else if (this.state.staleLabel) { - this.setState({ - staleLabel: false - }); } } - } /**