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 da973d158..f60ecb864 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 0799ef0cb..658d44585 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. diff --git a/react/features/always-on-top/AlwaysOnTop.js b/react/features/always-on-top/AlwaysOnTop.js index 7e66009fe..dffccbcf9 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()}. * diff --git a/react/features/base/dialog/components/AbstractDialog.js b/react/features/base/dialog/components/AbstractDialog.js index 3b32ea257..2a30bcf78 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; } diff --git a/react/features/base/dialog/components/StatelessDialog.web.js b/react/features/base/dialog/components/StatelessDialog.web.js index f00be7519..6db62dd2d 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 ( - - { + return ( + + { - /** - * Atlaskit has this empty span (JustifySim) so... - */ - } - - - { buttons } - - - ); - }; + /** + * 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') } ); } diff --git a/react/features/base/label/components/CircularLabel.native.js b/react/features/base/label/components/CircularLabel.native.js index 864ac7694..6b6c5cd28 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#componentWillReceiveProps}. + * Implements {@code Component#componentDidMount}. * * @inheritdoc */ - componentWillReceiveProps(newProps: Props) { - this._maybeToggleAnimation(this.props, newProps); + componentDidMount() { + this._maybeToggleAnimation({}, this.props); + } + + /** + * Implements {@code Component#componentDidUpdate}. + * + * @inheritdoc + */ + componentDidUpdate(prevProps: Props) { + this._maybeToggleAnimation(prevProps, this.props); } /** diff --git a/react/features/base/react/components/native/SideBar.js b/react/features/base/react/components/native/SideBar.js index 7999fb625..4086df6d2 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, diff --git a/react/features/base/toolbox/components/AbstractButton.js b/react/features/base/toolbox/components/AbstractButton.js index de6f1c0e9..f3364ca5d 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/deep-linking/components/DeepLinkingMobilePage.web.js b/react/features/deep-linking/components/DeepLinkingMobilePage.web.js index 230c0484a..a5f117147 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. * diff --git a/react/features/desktop-picker/components/DesktopPicker.js b/react/features/desktop-picker/components/DesktopPicker.js index bbb6e2225..6f190a974 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; /** diff --git a/react/features/device-selection/components/AudioInputPreview.js b/react/features/device-selection/components/AudioInputPreview.js index 987d38995..2953568c4 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); } } diff --git a/react/features/device-selection/components/DeviceSelection.js b/react/features/device-selection/components/DeviceSelection.js index 2344896d4..ec5f03439 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); } } diff --git a/react/features/invite/components/InfoDialogButton.web.js b/react/features/invite/components/InfoDialogButton.web.js index 06e2cccb0..916232b05 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; /** diff --git a/react/features/invite/components/info-dialog/InfoDialog.web.js b/react/features/invite/components/info-dialog/InfoDialog.web.js index 9ae652c46..dbb7b1fcb 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()}. * diff --git a/react/features/invite/components/info-dialog/PasswordForm.web.js b/react/features/invite/components/info-dialog/PasswordForm.web.js index e5115271d..e75faea57 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); diff --git a/react/features/large-video/components/Labels.web.js b/react/features/large-video/components/Labels.web.js index 703e1a513..930337c61 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()}. * diff --git a/react/features/large-video/components/LargeVideoBackground.web.js b/react/features/large-video/components/LargeVideoBackground.web.js index ee502666f..d9f4c733f 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(); } diff --git a/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js b/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js index 5ce1ad455..6f20ffce3 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 = []; diff --git a/react/features/recording/components/AbstractRecordingLabel.js b/react/features/recording/components/AbstractRecordingLabel.js index 78fcb5c34..227e58cf7 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 - }); } } - } /** diff --git a/react/features/recording/components/LiveStream/AbstractStreamKeyForm.js b/react/features/recording/components/LiveStream/AbstractStreamKeyForm.js index 0a393267e..278a03c74 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 91606ff62..a97e9e52b 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/LiveStreamButton.js b/react/features/recording/components/LiveStream/web/LiveStreamButton.js index 3921440db..9345160c0 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/LiveStream/web/StreamKeyForm.js b/react/features/recording/components/LiveStream/web/StreamKeyForm.js index 9ce96d621..334029d00 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 ?

{ 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 || ''; } /** diff --git a/react/features/speaker-stats/components/SpeakerStats.js b/react/features/speaker-stats/components/SpeakerStats.js index c191f56ce..56f565929 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); } diff --git a/react/features/toolbox/components/web/Toolbox.js b/react/features/toolbox/components/web/Toolbox.js index 88890386e..026fd18df 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)); } diff --git a/react/features/welcome/components/AbstractWelcomePage.js b/react/features/welcome/components/AbstractWelcomePage.js index b67ca3f38..02d089ca2 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. diff --git a/react/features/welcome/components/BlankPage.native.js b/react/features/welcome/components/BlankPage.native.js index 60c5e9a0e..8a71af59f 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()); } diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js index b9afe2250..ae9b3f1cf 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;