diff --git a/code_style.md b/code_style.md index 4b2338064c..3ad0d38873 100644 --- a/code_style.md +++ b/code_style.md @@ -174,12 +174,6 @@ React // Best, if onFooClick would do anything other than directly calling doStuff ``` - Not doing so is acceptable in a single case: in function-refs: - - ```jsx - this.component = self}> - ``` - - Prefer classes that extend `React.Component` (or `React.PureComponent`) instead of `React.createClass` - You can avoid the need to bind handler functions by using [property initializers](https://reactjs.org/docs/react-component.html#constructor): @@ -208,3 +202,5 @@ React ``` - Think about whether your component really needs state: are you duplicating information in component state that could be derived from the model? + +- Avoid things marked as Legacy or Deprecated in React 16 (e.g string refs and legacy contexts) diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/ExportE2eKeysDialog.js index 0fd412935a..ba2e985889 100644 --- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ExportE2eKeysDialog.js @@ -15,7 +15,7 @@ limitations under the License. */ import FileSaver from 'file-saver'; -import React from 'react'; +import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; @@ -44,6 +44,9 @@ export default createReactClass({ componentWillMount: function() { this._unmounted = false; + + this._passphrase1 = createRef(); + this._passphrase2 = createRef(); }, componentWillUnmount: function() { @@ -53,8 +56,8 @@ export default createReactClass({ _onPassphraseFormSubmit: function(ev) { ev.preventDefault(); - const passphrase = this.refs.passphrase1.value; - if (passphrase !== this.refs.passphrase2.value) { + const passphrase = this._passphrase1.current.value; + if (passphrase !== this._passphrase2.current.value) { this.setState({errStr: _t('Passphrases must match')}); return false; } @@ -148,7 +151,7 @@ export default createReactClass({
- @@ -161,7 +164,7 @@ export default createReactClass({
- diff --git a/src/async-components/views/dialogs/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/ImportE2eKeysDialog.js index 17f3bba117..de9e819f5a 100644 --- a/src/async-components/views/dialogs/ImportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ImportE2eKeysDialog.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; @@ -56,6 +56,9 @@ export default createReactClass({ componentWillMount: function() { this._unmounted = false; + + this._file = createRef(); + this._passphrase = createRef(); }, componentWillUnmount: function() { @@ -63,15 +66,15 @@ export default createReactClass({ }, _onFormChange: function(ev) { - const files = this.refs.file.files || []; + const files = this._file.current.files || []; this.setState({ - enableSubmit: (this.refs.passphrase.value !== "" && files.length > 0), + enableSubmit: (this._passphrase.current.value !== "" && files.length > 0), }); }, _onFormSubmit: function(ev) { ev.preventDefault(); - this._startImport(this.refs.file.files[0], this.refs.passphrase.value); + this._startImport(this._file.current.files[0], this._passphrase.current.value); return false; }, @@ -146,7 +149,10 @@ export default createReactClass({
- @@ -159,8 +165,11 @@ export default createReactClass({
-
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index a0aa36803f..d52599abe9 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -1214,25 +1214,25 @@ export default createReactClass({ const EditableText = sdk.getComponent("elements.EditableText"); - nameNode = ; + nameNode = ; - shortDescNode = ; + shortDescNode = ; } else { const onGroupHeaderItemClick = this.state.isUserMember ? this._onEditClick : null; const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null; diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js index e1b02f653b..1981310a2f 100644 --- a/src/components/structures/InteractiveAuth.js +++ b/src/components/structures/InteractiveAuth.js @@ -18,7 +18,7 @@ limitations under the License. import Matrix from 'matrix-js-sdk'; const InteractiveAuth = Matrix.InteractiveAuth; -import React from 'react'; +import React, {createRef} from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; @@ -129,6 +129,8 @@ export default createReactClass({ this._authLogic.poll(); }, 2000); } + + this._stageComponent = createRef(); }, componentWillUnmount: function() { @@ -153,8 +155,8 @@ export default createReactClass({ }, tryContinue: function() { - if (this.refs.stageComponent && this.refs.stageComponent.tryContinue) { - this.refs.stageComponent.tryContinue(); + if (this._stageComponent.current && this._stageComponent.current.tryContinue) { + this._stageComponent.current.tryContinue(); } }, @@ -192,8 +194,8 @@ export default createReactClass({ }, _setFocus: function() { - if (this.refs.stageComponent && this.refs.stageComponent.focus) { - this.refs.stageComponent.focus(); + if (this._stageComponent.current && this._stageComponent.current.focus) { + this._stageComponent.current.focus(); } }, @@ -214,7 +216,8 @@ export default createReactClass({ const StageComponent = getEntryComponentForLoginType(stage); return ( - { hr } @@ -829,14 +831,14 @@ export default class MessagePanel extends React.Component { // once dynamic content in the events load, make the scrollPanel check the // scroll offsets. _onHeightChanged = () => { - const scrollPanel = this.refs.scrollPanel; + const scrollPanel = this._scrollPanel.current; if (scrollPanel) { scrollPanel.checkScroll(); } }; _onTypingShown = () => { - const scrollPanel = this.refs.scrollPanel; + const scrollPanel = this._scrollPanel.current; // this will make the timeline grow, so checkScroll scrollPanel.checkScroll(); if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) { @@ -845,7 +847,7 @@ export default class MessagePanel extends React.Component { }; _onTypingHidden = () => { - const scrollPanel = this.refs.scrollPanel; + const scrollPanel = this._scrollPanel.current; if (scrollPanel) { // as hiding the typing notifications doesn't // update the scrollPanel, we tell it to apply @@ -858,11 +860,11 @@ export default class MessagePanel extends React.Component { }; updateTimelineMinHeight() { - const scrollPanel = this.refs.scrollPanel; + const scrollPanel = this._scrollPanel.current; if (scrollPanel) { const isAtBottom = scrollPanel.isAtBottom(); - const whoIsTyping = this.refs.whoIsTyping; + const whoIsTyping = this._whoIsTyping.current; const isTypingVisible = whoIsTyping && whoIsTyping.isVisible(); // when messages get added to the timeline, // but somebody else is still typing, @@ -875,7 +877,7 @@ export default class MessagePanel extends React.Component { } onTimelineReset() { - const scrollPanel = this.refs.scrollPanel; + const scrollPanel = this._scrollPanel.current; if (scrollPanel) { scrollPanel.clearPreventShrinking(); } @@ -909,19 +911,22 @@ export default class MessagePanel extends React.Component { room={this.props.room} onShown={this._onTypingShown} onHidden={this._onTypingHidden} - ref="whoIsTyping" /> + ref={this._whoIsTyping} /> ); } return ( - + { topSpinner } { this._getEventTiles() } { whoIsTyping } diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index efca8d12a8..c8863773f4 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -572,7 +572,7 @@ module.exports = createReactClass({ if (rows.length === 0 && !this.state.loading) { scrollpanel_content = { _t('No rooms to show') }; } else { - scrollpanel_content = + scrollpanel_content =
{ rows } diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 921680b678..2fbd19c428 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -18,7 +18,6 @@ limitations under the License. */ import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import classNames from 'classnames'; import sdk from '../../index'; import dis from '../../dispatcher'; @@ -36,12 +35,11 @@ import {_t} from "../../languageHandler"; // turn this on for drop & drag console debugging galore const debug = false; -const RoomSubList = createReactClass({ - displayName: 'RoomSubList', +export default class RoomSubList extends React.PureComponent { + static displayName = 'RoomSubList'; + static debug = debug; - debug: debug, - - propTypes: { + static propTypes = { list: PropTypes.arrayOf(PropTypes.object).isRequired, label: PropTypes.string.isRequired, tagName: PropTypes.string, @@ -59,10 +57,26 @@ const RoomSubList = createReactClass({ incomingCall: PropTypes.object, extraTiles: PropTypes.arrayOf(PropTypes.node), // extra elements added beneath tiles forceExpand: PropTypes.bool, - }, + }; - getInitialState: function() { + static defaultProps = { + onHeaderClick: function() { + }, // NOP + extraTiles: [], + isInvite: false, + }; + + static getDerivedStateFromProps(props, state) { return { + listLength: props.list.length, + scrollTop: props.list.length === state.listLength ? state.scrollTop : 0, + }; + } + + constructor(props) { + super(props); + + this.state = { hidden: this.props.startAsHidden || false, // some values to get LazyRenderList starting scrollerHeight: 800, @@ -71,47 +85,33 @@ const RoomSubList = createReactClass({ // we have to store the length of the list here so we can see if it's changed or not... listLength: null, }; - }, - getDefaultProps: function() { - return { - onHeaderClick: function() { - }, // NOP - extraTiles: [], - isInvite: false, - }; - }, - - componentDidMount: function() { + this._header = createRef(); + this._subList = createRef(); + this._scroller = createRef(); this._headerButton = createRef(); + } + + componentDidMount() { this.dispatcherRef = dis.register(this.onAction); - }, + } - statics: { - getDerivedStateFromProps: function(props, state) { - return { - listLength: props.list.length, - scrollTop: props.list.length === state.listLength ? state.scrollTop : 0, - }; - }, - }, - - componentWillUnmount: function() { + componentWillUnmount() { dis.unregister(this.dispatcherRef); - }, + } // The header is collapsible if it is hidden or not stuck // The dataset elements are added in the RoomList _initAndPositionStickyHeaders method - isCollapsibleOnClick: function() { - const stuck = this.refs.header.dataset.stuck; + isCollapsibleOnClick() { + const stuck = this._header.current.dataset.stuck; if (!this.props.forceExpand && (this.state.hidden || stuck === undefined || stuck === "none")) { return true; } else { return false; } - }, + } - onAction: function(payload) { + onAction = (payload) => { // XXX: Previously RoomList would forceUpdate whenever on_room_read is dispatched, // but this is no longer true, so we must do it here (and can apply the small // optimisation of checking that we care about the room being read). @@ -124,9 +124,9 @@ const RoomSubList = createReactClass({ ) { this.forceUpdate(); } - }, + }; - onClick: function(ev) { + onClick = (ev) => { if (this.isCollapsibleOnClick()) { // The header isCollapsible, so the click is to be interpreted as collapse and truncation logic const isHidden = !this.state.hidden; @@ -135,11 +135,11 @@ const RoomSubList = createReactClass({ }); } else { // The header is stuck, so the click is to be interpreted as a scroll to the header - this.props.onHeaderClick(this.state.hidden, this.refs.header.dataset.originalPosition); + this.props.onHeaderClick(this.state.hidden, this._header.current.dataset.originalPosition); } - }, + }; - onHeaderKeyDown: function(ev) { + onHeaderKeyDown = (ev) => { switch (ev.key) { case Key.TAB: // Prevent LeftPanel handling Tab if focus is on the sublist header itself @@ -159,7 +159,7 @@ const RoomSubList = createReactClass({ this.onClick(); } else if (!this.props.forceExpand) { // sublist is expanded, go to first room - const element = this.refs.subList && this.refs.subList.querySelector(".mx_RoomTile"); + const element = this._subList.current && this._subList.current.querySelector(".mx_RoomTile"); if (element) { element.focus(); } @@ -167,9 +167,9 @@ const RoomSubList = createReactClass({ break; } } - }, + }; - onKeyDown: function(ev) { + onKeyDown = (ev) => { switch (ev.key) { // On ARROW_LEFT go to the sublist header case Key.ARROW_LEFT: @@ -180,24 +180,24 @@ const RoomSubList = createReactClass({ case Key.ARROW_RIGHT: ev.stopPropagation(); } - }, + }; - onRoomTileClick(roomId, ev) { + onRoomTileClick = (roomId, ev) => { dis.dispatch({ action: 'view_room', room_id: roomId, clear_search: (ev && (ev.keyCode === KeyCode.ENTER || ev.keyCode === KeyCode.SPACE)), }); - }, + }; - _updateSubListCount: function() { + _updateSubListCount = () => { // Force an update by setting the state to the current state // Doing it this way rather than using forceUpdate(), so that the shouldComponentUpdate() // method is honoured this.setState(this.state); - }, + }; - makeRoomTile: function(room) { + makeRoomTile = (room) => { return ; - }, + }; - _onNotifBadgeClick: function(e) { + _onNotifBadgeClick = (e) => { // prevent the roomsublist collapsing e.preventDefault(); e.stopPropagation(); @@ -225,9 +225,9 @@ const RoomSubList = createReactClass({ room_id: room.roomId, }); } - }, + }; - _onInviteBadgeClick: function(e) { + _onInviteBadgeClick = (e) => { // prevent the roomsublist collapsing e.preventDefault(); e.stopPropagation(); @@ -247,14 +247,14 @@ const RoomSubList = createReactClass({ }); } } - }, + }; - onAddRoom: function(e) { + onAddRoom = (e) => { e.stopPropagation(); if (this.props.onAddRoom) this.props.onAddRoom(); - }, + }; - _getHeaderJsx: function(isCollapsed) { + _getHeaderJsx(isCollapsed) { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleTooltipButton = sdk.getComponent('elements.AccessibleTooltipButton'); const subListNotifications = !this.props.isInvite ? @@ -328,7 +328,7 @@ const RoomSubList = createReactClass({ } return ( -
+
); - }, + } - checkOverflow: function() { - if (this.refs.scroller) { - this.refs.scroller.checkOverflow(); + checkOverflow = () => { + if (this._scroller.current) { + this._scroller.current.checkOverflow(); } - }, + }; - setHeight: function(height) { - if (this.refs.subList) { - this.refs.subList.style.height = `${height}px`; + setHeight = (height) => { + if (this._subList.current) { + this._subList.current.style.height = `${height}px`; } this._updateLazyRenderHeight(height); - }, + }; - _updateLazyRenderHeight: function(height) { + _updateLazyRenderHeight(height) { this.setState({scrollerHeight: height}); - }, + } - _onScroll: function() { - this.setState({scrollTop: this.refs.scroller.getScrollTop()}); - }, + _onScroll = () => { + this.setState({scrollTop: this._scroller.current.getScrollTop()}); + }; _canUseLazyListRendering() { // for now disable lazy rendering as they are already rendered tiles // not rooms like props.list we pass to LazyRenderList return !this.props.extraTiles || !this.props.extraTiles.length; - }, + } - render: function() { + render() { const len = this.props.list.length + this.props.extraTiles.length; const isCollapsed = this.state.hidden && !this.props.forceExpand; @@ -391,7 +391,7 @@ const RoomSubList = createReactClass({ // no body } else if (this._canUseLazyListRendering()) { content = ( - + this.makeRoomTile(r)); const tiles = roomTiles.concat(this.props.extraTiles); content = ( - + { tiles } ); @@ -418,7 +418,7 @@ const RoomSubList = createReactClass({ return (
); - }, -}); - -module.exports = RoomSubList; + } +} diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 5cc1e2b765..b2c39a1225 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -23,7 +23,7 @@ limitations under the License. import shouldHideEvent from '../../shouldHideEvent'; -import React from 'react'; +import React, {createRef} from 'react'; import createReactClass from 'create-react-class'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; @@ -207,6 +207,9 @@ module.exports = createReactClass({ this._onCiderUpdated(); this._ciderWatcherRef = SettingsStore.watchSetting( "useCiderComposer", null, this._onCiderUpdated); + + this._roomView = createRef(); + this._searchResultsPanel = createRef(); }, _onCiderUpdated: function() { @@ -459,8 +462,8 @@ module.exports = createReactClass({ }, componentDidUpdate: function() { - if (this.refs.roomView) { - const roomView = ReactDOM.findDOMNode(this.refs.roomView); + if (this._roomView.current) { + const roomView = ReactDOM.findDOMNode(this._roomView.current); if (!roomView.ondrop) { roomView.addEventListener('drop', this.onDrop); roomView.addEventListener('dragover', this.onDragOver); @@ -474,10 +477,10 @@ module.exports = createReactClass({ // in render() prevents the ref from being set on first mount, so we try and // catch the messagePanel when it does mount. Because we only want the ref once, // we use a boolean flag to avoid duplicate work. - if (this.refs.messagePanel && !this.state.atEndOfLiveTimelineInit) { + if (this._messagePanel && !this.state.atEndOfLiveTimelineInit) { this.setState({ atEndOfLiveTimelineInit: true, - atEndOfLiveTimeline: this.refs.messagePanel.isAtEndOfLiveTimeline(), + atEndOfLiveTimeline: this._messagePanel.isAtEndOfLiveTimeline(), }); } }, @@ -499,12 +502,12 @@ module.exports = createReactClass({ // stop tracking room changes to format permalinks this._stopAllPermalinkCreators(); - if (this.refs.roomView) { + if (this._roomView.current) { // disconnect the D&D event listeners from the room view. This // is really just for hygiene - we're going to be // deleted anyway, so it doesn't matter if the event listeners // don't get cleaned up. - const roomView = ReactDOM.findDOMNode(this.refs.roomView); + const roomView = ReactDOM.findDOMNode(this._roomView.current); roomView.removeEventListener('drop', this.onDrop); roomView.removeEventListener('dragover', this.onDragOver); roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd); @@ -701,10 +704,10 @@ module.exports = createReactClass({ }, canResetTimeline: function() { - if (!this.refs.messagePanel) { + if (!this._messagePanel) { return true; } - return this.refs.messagePanel.canResetTimeline(); + return this._messagePanel.canResetTimeline(); }, // called when state.room is first initialised (either at initial load, @@ -1046,7 +1049,7 @@ module.exports = createReactClass({ }, onMessageListScroll: function(ev) { - if (this.refs.messagePanel.isAtEndOfLiveTimeline()) { + if (this._messagePanel.isAtEndOfLiveTimeline()) { this.setState({ numUnreadMessages: 0, atEndOfLiveTimeline: true, @@ -1119,8 +1122,8 @@ module.exports = createReactClass({ // if we already have a search panel, we need to tell it to forget // about its scroll state. - if (this.refs.searchResultsPanel) { - this.refs.searchResultsPanel.resetScrollState(); + if (this._searchResultsPanel.current) { + this._searchResultsPanel.current.resetScrollState(); } // make sure that we don't end up showing results from @@ -1225,7 +1228,7 @@ module.exports = createReactClass({ // once dynamic content in the search results load, make the scrollPanel check // the scroll offsets. const onHeightChanged = () => { - const scrollPanel = this.refs.searchResultsPanel; + const scrollPanel = this._searchResultsPanel.current; if (scrollPanel) { scrollPanel.checkScroll(); } @@ -1370,28 +1373,28 @@ module.exports = createReactClass({ // jump down to the bottom of this room, where new events are arriving jumpToLiveTimeline: function() { - this.refs.messagePanel.jumpToLiveTimeline(); + this._messagePanel.jumpToLiveTimeline(); dis.dispatch({action: 'focus_composer'}); }, // jump up to wherever our read marker is jumpToReadMarker: function() { - this.refs.messagePanel.jumpToReadMarker(); + this._messagePanel.jumpToReadMarker(); }, // update the read marker to match the read-receipt forgetReadMarker: function(ev) { ev.stopPropagation(); - this.refs.messagePanel.forgetReadMarker(); + this._messagePanel.forgetReadMarker(); }, // decide whether or not the top 'unread messages' bar should be shown _updateTopUnreadMessagesBar: function() { - if (!this.refs.messagePanel) { + if (!this._messagePanel) { return; } - const showBar = this.refs.messagePanel.canJumpToReadMarker(); + const showBar = this._messagePanel.canJumpToReadMarker(); if (this.state.showTopUnreadMessagesBar != showBar) { this.setState({showTopUnreadMessagesBar: showBar}); } @@ -1401,7 +1404,7 @@ module.exports = createReactClass({ // restored when we switch back to it. // _getScrollState: function() { - const messagePanel = this.refs.messagePanel; + const messagePanel = this._messagePanel; if (!messagePanel) return null; // if we're following the live timeline, we want to return null; that @@ -1506,10 +1509,10 @@ module.exports = createReactClass({ */ handleScrollKey: function(ev) { let panel; - if (this.refs.searchResultsPanel) { - panel = this.refs.searchResultsPanel; - } else if (this.refs.messagePanel) { - panel = this.refs.messagePanel; + if (this._searchResultsPanel.current) { + panel = this._searchResultsPanel.current; + } else if (this._messagePanel) { + panel = this._messagePanel; } if (panel) { @@ -1530,7 +1533,7 @@ module.exports = createReactClass({ // this has to be a proper method rather than an unnamed function, // otherwise react calls it with null on each update. _gatherTimelinePanelRef: function(r) { - this.refs.messagePanel = r; + this._messagePanel = r; if (r) { console.log("updateTint from RoomView._gatherTimelinePanelRef"); this.updateTint(); @@ -1719,7 +1722,7 @@ module.exports = createReactClass({ aux = ; } else if (this.state.searching) { hideCancel = true; // has own cancel - aux = ; + aux = ; } else if (showRoomUpgradeBar) { aux = ; hideCancel = true; @@ -1775,7 +1778,7 @@ module.exports = createReactClass({ } const auxPanel = ( - ); } else { searchResultsPanel = ( - +
- = 0; --i) { @@ -756,7 +758,7 @@ module.exports = createReactClass({ }, _getMessagesHeight() { - const itemlist = this.refs.itemlist; + const itemlist = this._itemlist.current; const lastNode = itemlist.lastElementChild; const lastNodeBottom = lastNode ? lastNode.offsetTop + lastNode.clientHeight : 0; const firstNodeTop = itemlist.firstElementChild ? itemlist.firstElementChild.offsetTop : 0; @@ -765,7 +767,7 @@ module.exports = createReactClass({ }, _topFromBottom(node) { - return this.refs.itemlist.clientHeight - node.offsetTop; + return this._itemlist.current.clientHeight - node.offsetTop; }, /* get the DOM node which has the scrollTop property we care about for our @@ -797,7 +799,7 @@ module.exports = createReactClass({ the same minimum bottom offset, effectively preventing the timeline to shrink. */ preventShrinking: function() { - const messageList = this.refs.itemlist; + const messageList = this._itemlist.current; const tiles = messageList && messageList.children; if (!messageList) { return; @@ -824,7 +826,7 @@ module.exports = createReactClass({ /** Clear shrinking prevention. Used internally, and when the timeline is reloaded. */ clearPreventShrinking: function() { - const messageList = this.refs.itemlist; + const messageList = this._itemlist.current; const balanceElement = messageList && messageList.parentElement; if (balanceElement) balanceElement.style.paddingBottom = null; this.preventShrinkingState = null; @@ -843,7 +845,7 @@ module.exports = createReactClass({ if (this.preventShrinkingState) { const sn = this._getScrollNode(); const scrollState = this.scrollState; - const messageList = this.refs.itemlist; + const messageList = this._itemlist.current; const {offsetNode, offsetFromBottom} = this.preventShrinkingState; // element used to set paddingBottom to balance the typing notifs disappearing const balanceElement = messageList.parentElement; @@ -879,7 +881,7 @@ module.exports = createReactClass({ onScroll={this.onScroll} className={`mx_ScrollPanel ${this.props.className}`} style={this.props.style}>
-
    +
      { this.props.children }
diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index 21613733db..0aa2e15f4c 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, {createRef} from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import { KeyCode } from '../../Keyboard'; @@ -53,6 +53,10 @@ module.exports = createReactClass({ }; }, + UNSAFE_componentWillMount: function() { + this._search = createRef(); + }, + componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); }, @@ -66,26 +70,26 @@ module.exports = createReactClass({ switch (payload.action) { case 'view_room': - if (this.refs.search && payload.clear_search) { + if (this._search.current && payload.clear_search) { this._clearSearch(); } break; case 'focus_room_filter': - if (this.refs.search) { - this.refs.search.focus(); + if (this._search.current) { + this._search.current.focus(); } break; } }, onChange: function() { - if (!this.refs.search) return; - this.setState({ searchTerm: this.refs.search.value }); + if (!this._search.current) return; + this.setState({ searchTerm: this._search.current.value }); this.onSearch(); }, onSearch: throttle(function() { - this.props.onSearch(this.refs.search.value); + this.props.onSearch(this._search.current.value); }, 200, {trailing: true, leading: true}), _onKeyDown: function(ev) { @@ -113,7 +117,7 @@ module.exports = createReactClass({ }, _clearSearch: function(source) { - this.refs.search.value = ""; + this._search.current.value = ""; this.onChange(); if (this.props.onCleared) { this.props.onCleared(source); @@ -146,7 +150,7 @@ module.exports = createReactClass({ { - if (payload.event && this.refs.messagePanel) { - this.refs.messagePanel.scrollToEventIfNeeded( + if (payload.event && this._messagePanel.current) { + this._messagePanel.current.scrollToEventIfNeeded( payload.event.getId(), ); } @@ -442,9 +444,9 @@ const TimelinePanel = createReactClass({ // updates from pagination will happen when the paginate completes. if (toStartOfTimeline || !data || !data.liveEvent) return; - if (!this.refs.messagePanel) return; + if (!this._messagePanel.current) return; - if (!this.refs.messagePanel.getScrollState().stuckAtBottom) { + if (!this._messagePanel.current.getScrollState().stuckAtBottom) { // we won't load this event now, because we don't want to push any // events off the other end of the timeline. But we need to note // that we can now paginate. @@ -499,7 +501,7 @@ const TimelinePanel = createReactClass({ } this.setState(updatedState, () => { - this.refs.messagePanel.updateTimelineMinHeight(); + this._messagePanel.current.updateTimelineMinHeight(); if (callRMUpdated) { this.props.onReadMarkerUpdated(); } @@ -510,13 +512,13 @@ const TimelinePanel = createReactClass({ onRoomTimelineReset: function(room, timelineSet) { if (timelineSet !== this.props.timelineSet) return; - if (this.refs.messagePanel && this.refs.messagePanel.isAtBottom()) { + if (this._messagePanel.current && this._messagePanel.current.isAtBottom()) { this._loadTimeline(); } }, canResetTimeline: function() { - return this.refs.messagePanel && this.refs.messagePanel.isAtBottom(); + return this._messagePanel.current && this._messagePanel.current.isAtBottom(); }, onRoomRedaction: function(ev, room) { @@ -629,7 +631,7 @@ const TimelinePanel = createReactClass({ sendReadReceipt: function() { if (SettingsStore.getValue("lowBandwidth")) return; - if (!this.refs.messagePanel) return; + if (!this._messagePanel.current) return; if (!this.props.manageReadReceipts) return; // This happens on user_activity_end which is delayed, and it's // very possible have logged out within that timeframe, so check @@ -815,8 +817,8 @@ const TimelinePanel = createReactClass({ if (this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) { this._loadTimeline(); } else { - if (this.refs.messagePanel) { - this.refs.messagePanel.scrollToBottom(); + if (this._messagePanel.current) { + this._messagePanel.current.scrollToBottom(); } } }, @@ -826,7 +828,7 @@ const TimelinePanel = createReactClass({ */ jumpToReadMarker: function() { if (!this.props.manageReadMarkers) return; - if (!this.refs.messagePanel) return; + if (!this._messagePanel.current) return; if (!this.state.readMarkerEventId) return; // we may not have loaded the event corresponding to the read-marker @@ -835,11 +837,11 @@ const TimelinePanel = createReactClass({ // // a quick way to figure out if we've loaded the relevant event is // simply to check if the messagepanel knows where the read-marker is. - const ret = this.refs.messagePanel.getReadMarkerPosition(); + const ret = this._messagePanel.current.getReadMarkerPosition(); if (ret !== null) { // The messagepanel knows where the RM is, so we must have loaded // the relevant event. - this.refs.messagePanel.scrollToEvent(this.state.readMarkerEventId, + this._messagePanel.current.scrollToEvent(this.state.readMarkerEventId, 0, 1/3); return; } @@ -874,8 +876,8 @@ const TimelinePanel = createReactClass({ * at the end of the live timeline. */ isAtEndOfLiveTimeline: function() { - return this.refs.messagePanel - && this.refs.messagePanel.isAtBottom() + return this._messagePanel.current + && this._messagePanel.current.isAtBottom() && this._timelineWindow && !this._timelineWindow.canPaginate(EventTimeline.FORWARDS); }, @@ -887,8 +889,8 @@ const TimelinePanel = createReactClass({ * returns null if we are not mounted. */ getScrollState: function() { - if (!this.refs.messagePanel) { return null; } - return this.refs.messagePanel.getScrollState(); + if (!this._messagePanel.current) { return null; } + return this._messagePanel.current.getScrollState(); }, // returns one of: @@ -899,9 +901,9 @@ const TimelinePanel = createReactClass({ // +1: read marker is below the window getReadMarkerPosition: function() { if (!this.props.manageReadMarkers) return null; - if (!this.refs.messagePanel) return null; + if (!this._messagePanel.current) return null; - const ret = this.refs.messagePanel.getReadMarkerPosition(); + const ret = this._messagePanel.current.getReadMarkerPosition(); if (ret !== null) { return ret; } @@ -936,7 +938,7 @@ const TimelinePanel = createReactClass({ * We pass it down to the scroll panel. */ handleScrollKey: function(ev) { - if (!this.refs.messagePanel) { return; } + if (!this._messagePanel.current) { return; } // jump to the live timeline on ctrl-end, rather than the end of the // timeline window. @@ -944,7 +946,7 @@ const TimelinePanel = createReactClass({ ev.keyCode == KeyCode.END) { this.jumpToLiveTimeline(); } else { - this.refs.messagePanel.handleScrollKey(ev); + this._messagePanel.current.handleScrollKey(ev); } }, @@ -986,8 +988,8 @@ const TimelinePanel = createReactClass({ const onLoaded = () => { // clear the timeline min-height when // (re)loading the timeline - if (this.refs.messagePanel) { - this.refs.messagePanel.onTimelineReset(); + if (this._messagePanel.current) { + this._messagePanel.current.onTimelineReset(); } this._reloadEvents(); @@ -1002,7 +1004,7 @@ const TimelinePanel = createReactClass({ timelineLoading: false, }, () => { // initialise the scroll state of the message panel - if (!this.refs.messagePanel) { + if (!this._messagePanel.current) { // this shouldn't happen - we know we're mounted because // we're in a setState callback, and we know // timelineLoading is now false, so render() should have @@ -1012,10 +1014,10 @@ const TimelinePanel = createReactClass({ return; } if (eventId) { - this.refs.messagePanel.scrollToEvent(eventId, pixelOffset, + this._messagePanel.current.scrollToEvent(eventId, pixelOffset, offsetBase); } else { - this.refs.messagePanel.scrollToBottom(); + this._messagePanel.current.scrollToBottom(); } this.sendReadReceipt(); @@ -1134,7 +1136,7 @@ const TimelinePanel = createReactClass({ const ignoreOwn = opts.ignoreOwn || false; const allowPartial = opts.allowPartial || false; - const messagePanel = this.refs.messagePanel; + const messagePanel = this._messagePanel.current; if (messagePanel === undefined) return null; const EventTile = sdk.getComponent('rooms.EventTile'); @@ -1313,7 +1315,8 @@ const TimelinePanel = createReactClass({ ['PREPARED', 'CATCHUP'].includes(this.state.clientSyncState) ); return ( -