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/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/RoomSubList.js b/src/components/structures/RoomSubList.js index 921680b678..fe43e60405 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -82,8 +82,14 @@ const RoomSubList = createReactClass({ }; }, - componentDidMount: function() { + UNSAFE_componentWillMount: function() { + this._header = createRef(); + this._subList = createRef(); + this._scroller = createRef(); this._headerButton = createRef(); + }, + + componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); }, @@ -103,7 +109,7 @@ const RoomSubList = createReactClass({ // 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; + const stuck = this._header.current.dataset.stuck; if (!this.props.forceExpand && (this.state.hidden || stuck === undefined || stuck === "none")) { return true; } else { @@ -135,7 +141,7 @@ 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); } }, @@ -159,7 +165,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(); } @@ -328,7 +334,7 @@ const RoomSubList = createReactClass({ } return ( -
+
+ this.makeRoomTile(r)); const tiles = roomTiles.concat(this.props.extraTiles); content = ( - + { tiles } ); @@ -418,7 +424,7 @@ const RoomSubList = createReactClass({ return (
{ - 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(); @@ -1875,7 +1878,7 @@ module.exports = createReactClass({ searchResultsPanel = (
); } 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 ( -