Merge pull request #3712 from matrix-org/t3chguy/react16_refs

Migrate away from React Legacy string refs
This commit is contained in:
Michael Telatynski 2019-12-10 17:43:37 +00:00 committed by GitHub
commit c1b1f98201
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 536 additions and 407 deletions

View file

@ -1214,25 +1214,25 @@ export default createReactClass({
const EditableText = sdk.getComponent("elements.EditableText");
nameNode = <EditableText ref="nameEditor"
className="mx_GroupView_editable"
placeholderClassName="mx_GroupView_placeholder"
placeholder={_t('Community Name')}
blurToCancel={false}
initialValue={this.state.profileForm.name}
onValueChanged={this._onNameChange}
tabIndex="0"
dir="auto" />;
nameNode = <EditableText
className="mx_GroupView_editable"
placeholderClassName="mx_GroupView_placeholder"
placeholder={_t('Community Name')}
blurToCancel={false}
initialValue={this.state.profileForm.name}
onValueChanged={this._onNameChange}
tabIndex="0"
dir="auto" />;
shortDescNode = <EditableText ref="descriptionEditor"
className="mx_GroupView_editable"
placeholderClassName="mx_GroupView_placeholder"
placeholder={_t("Description")}
blurToCancel={false}
initialValue={this.state.profileForm.short_description}
onValueChanged={this._onShortDescChange}
tabIndex="0"
dir="auto" />;
shortDescNode = <EditableText
className="mx_GroupView_editable"
placeholderClassName="mx_GroupView_placeholder"
placeholder={_t("Description")}
blurToCancel={false}
initialValue={this.state.profileForm.short_description}
onValueChanged={this._onShortDescChange}
tabIndex="0"
dir="auto" />;
} else {
const onGroupHeaderItemClick = this.state.isUserMember ? this._onEditClick : null;
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;

View file

@ -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 (
<StageComponent ref="stageComponent"
<StageComponent
ref={this._stageComponent}
loginType={stage}
matrixClient={this.props.matrixClient}
authSessionId={this._authLogic.getSessionId()}

View file

@ -17,7 +17,7 @@ limitations under the License.
*/
import { MatrixClient } from 'matrix-js-sdk';
import React from 'react';
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { DragDropContext } from 'react-beautiful-dnd';
@ -129,6 +129,8 @@ const LoggedInView = createReactClass({
this._matrixClient.on("RoomState.events", this.onRoomStateEvents);
fixupColorFonts();
this._roomView = createRef();
},
componentDidUpdate(prevProps) {
@ -165,10 +167,10 @@ const LoggedInView = createReactClass({
},
canResetTimelineInRoom: function(roomId) {
if (!this.refs.roomView) {
if (!this._roomView.current) {
return true;
}
return this.refs.roomView.canResetTimeline();
return this._roomView.current.canResetTimeline();
},
_setStateFromSessionStore() {
@ -428,8 +430,8 @@ const LoggedInView = createReactClass({
* @param {Object} ev The key event
*/
_onScrollKeyPressed: function(ev) {
if (this.refs.roomView) {
this.refs.roomView.handleScrollKey(ev);
if (this._roomView.current) {
this._roomView.current.handleScrollKey(ev);
}
},
@ -543,7 +545,7 @@ const LoggedInView = createReactClass({
switch (this.props.page_type) {
case PageTypes.RoomView:
pageElement = <RoomView
ref='roomView'
ref={this._roomView}
autoJoin={this.props.autoJoin}
onRegistered={this.props.onRegistered}
thirdPartyInvite={this.props.thirdPartyInvite}

View file

@ -16,7 +16,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 ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
@ -159,6 +159,10 @@ export default class MessagePanel extends React.Component {
SettingsStore.getValue("showHiddenEventsInTimeline");
this._isMounted = false;
this._readMarkerNode = createRef();
this._whoIsTyping = createRef();
this._scrollPanel = createRef();
}
componentDidMount() {
@ -191,8 +195,7 @@ export default class MessagePanel extends React.Component {
/* return true if the content is fully scrolled down right now; else false.
*/
isAtBottom() {
return this.refs.scrollPanel
&& this.refs.scrollPanel.isAtBottom();
return this._scrollPanel.current && this._scrollPanel.current.isAtBottom();
}
/* get the current scroll state. See ScrollPanel.getScrollState for
@ -201,8 +204,7 @@ export default class MessagePanel extends React.Component {
* returns null if we are not mounted.
*/
getScrollState() {
if (!this.refs.scrollPanel) { return null; }
return this.refs.scrollPanel.getScrollState();
return this._scrollPanel.current ? this._scrollPanel.current.getScrollState() : null;
}
// returns one of:
@ -212,8 +214,8 @@ export default class MessagePanel extends React.Component {
// 0: read marker is within the window
// +1: read marker is below the window
getReadMarkerPosition() {
const readMarker = this.refs.readMarkerNode;
const messageWrapper = this.refs.scrollPanel;
const readMarker = this._readMarkerNode.current;
const messageWrapper = this._scrollPanel.current;
if (!readMarker || !messageWrapper) {
return null;
@ -236,16 +238,16 @@ export default class MessagePanel extends React.Component {
/* jump to the top of the content.
*/
scrollToTop() {
if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollToTop();
if (this._scrollPanel.current) {
this._scrollPanel.current.scrollToTop();
}
}
/* jump to the bottom of the content.
*/
scrollToBottom() {
if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollToBottom();
if (this._scrollPanel.current) {
this._scrollPanel.current.scrollToBottom();
}
}
@ -255,8 +257,8 @@ export default class MessagePanel extends React.Component {
* @param {number} mult: -1 to page up, +1 to page down
*/
scrollRelative(mult) {
if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollRelative(mult);
if (this._scrollPanel.current) {
this._scrollPanel.current.scrollRelative(mult);
}
}
@ -266,8 +268,8 @@ export default class MessagePanel extends React.Component {
* @param {KeyboardEvent} ev: the keyboard event to handle
*/
handleScrollKey(ev) {
if (this.refs.scrollPanel) {
this.refs.scrollPanel.handleScrollKey(ev);
if (this._scrollPanel.current) {
this._scrollPanel.current.handleScrollKey(ev);
}
}
@ -282,8 +284,8 @@ export default class MessagePanel extends React.Component {
* defaults to 0.
*/
scrollToEvent(eventId, pixelOffset, offsetBase) {
if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollToToken(eventId, pixelOffset, offsetBase);
if (this._scrollPanel.current) {
this._scrollPanel.current.scrollToToken(eventId, pixelOffset, offsetBase);
}
}
@ -297,8 +299,8 @@ export default class MessagePanel extends React.Component {
/* check the scroll state and send out pagination requests if necessary.
*/
checkFillState() {
if (this.refs.scrollPanel) {
this.refs.scrollPanel.checkFillState();
if (this._scrollPanel.current) {
this._scrollPanel.current.checkFillState();
}
}
@ -345,7 +347,7 @@ export default class MessagePanel extends React.Component {
}
return (
<li key={"readMarker_"+eventId} ref="readMarkerNode"
<li key={"readMarker_"+eventId} ref={this._readMarkerNode}
className="mx_RoomView_myReadMarker_container">
{ hr }
</li>
@ -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 (
<ScrollPanel ref="scrollPanel" className={className}
onScroll={this.props.onScroll}
onResize={this.onResize}
onFillRequest={this.props.onFillRequest}
onUnfillRequest={this.props.onUnfillRequest}
style={style}
stickyBottom={this.props.stickyBottom}
resizeNotifier={this.props.resizeNotifier}>
<ScrollPanel
ref={this._scrollPanel}
className={className}
onScroll={this.props.onScroll}
onResize={this.onResize}
onFillRequest={this.props.onFillRequest}
onUnfillRequest={this.props.onUnfillRequest}
style={style}
stickyBottom={this.props.stickyBottom}
resizeNotifier={this.props.resizeNotifier}
>
{ topSpinner }
{ this._getEventTiles() }
{ whoIsTyping }

View file

@ -572,7 +572,7 @@ module.exports = createReactClass({
if (rows.length === 0 && !this.state.loading) {
scrollpanel_content = <i>{ _t('No rooms to show') }</i>;
} else {
scrollpanel_content = <table ref="directory_table" className="mx_RoomDirectory_table">
scrollpanel_content = <table className="mx_RoomDirectory_table">
<tbody>
{ rows }
</tbody>

View file

@ -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 <RoomTile
room={room}
roomSubList={this}
@ -212,9 +212,9 @@ const RoomSubList = createReactClass({
incomingCall={null}
onClick={this.onRoomTileClick}
/>;
},
};
_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 (
<div className="mx_RoomSubList_labelContainer" title={title} ref="header" onKeyDown={this.onHeaderKeyDown}>
<div className="mx_RoomSubList_labelContainer" title={title} ref={this._header} onKeyDown={this.onHeaderKeyDown}>
<AccessibleButton
onClick={this.onClick}
className="mx_RoomSubList_label"
@ -346,36 +346,36 @@ const RoomSubList = createReactClass({
{ addRoomButton }
</div>
);
},
}
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 = (
<IndicatorScrollbar ref="scroller" className="mx_RoomSubList_scroll" onScroll={this._onScroll}>
<IndicatorScrollbar ref={this._scroller} className="mx_RoomSubList_scroll" onScroll={this._onScroll}>
<LazyRenderList
scrollTop={this.state.scrollTop }
height={ this.state.scrollerHeight }
@ -404,7 +404,7 @@ const RoomSubList = createReactClass({
const roomTiles = this.props.list.map(r => this.makeRoomTile(r));
const tiles = roomTiles.concat(this.props.extraTiles);
content = (
<IndicatorScrollbar ref="scroller" className="mx_RoomSubList_scroll" onScroll={this._onScroll}>
<IndicatorScrollbar ref={this._scroller} className="mx_RoomSubList_scroll" onScroll={this._onScroll}>
{ tiles }
</IndicatorScrollbar>
);
@ -418,7 +418,7 @@ const RoomSubList = createReactClass({
return (
<div
ref="subList"
ref={this._subList}
className={subListClasses}
role="group"
aria-label={this.props.label}
@ -428,7 +428,5 @@ const RoomSubList = createReactClass({
{ content }
</div>
);
},
});
module.exports = RoomSubList;
}
}

View file

@ -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 = <ForwardMessage onCancelClick={this.onCancelClick} />;
} else if (this.state.searching) {
hideCancel = true; // has own cancel
aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress} onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch} />;
aux = <SearchBar searchInProgress={this.state.searchInProgress} onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch} />;
} else if (showRoomUpgradeBar) {
aux = <RoomUpgradeWarningBar room={this.state.room} recommendation={roomVersionRecommendation} />;
hideCancel = true;
@ -1775,7 +1778,7 @@ module.exports = createReactClass({
}
const auxPanel = (
<AuxPanel ref="auxPanel" room={this.state.room}
<AuxPanel room={this.state.room}
fullHeight={false}
userId={MatrixClientPeg.get().credentials.userId}
conferenceHandler={this.props.ConferenceHandler}
@ -1875,7 +1878,7 @@ module.exports = createReactClass({
searchResultsPanel = (<div className="mx_RoomView_messagePanel mx_RoomView_messagePanelSearchSpinner" />);
} else {
searchResultsPanel = (
<ScrollPanel ref="searchResultsPanel"
<ScrollPanel ref={this._searchResultsPanel}
className="mx_RoomView_messagePanel mx_RoomView_searchResultsPanel"
onFillRequest={this.onSearchResultsFillRequest}
resizeNotifier={this.props.resizeNotifier}
@ -1898,7 +1901,8 @@ module.exports = createReactClass({
// console.info("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
const messagePanel = (
<TimelinePanel ref={this._gatherTimelinePanelRef}
<TimelinePanel
ref={this._gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()}
showReadReceipts={SettingsStore.getValue('showReadReceipts')}
manageReadReceipts={!this.state.isPeeking}
@ -1952,9 +1956,11 @@ module.exports = createReactClass({
const collapsedRhs = hideRightPanel || this.props.collapsedRhs;
return (
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref="roomView">
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref={this._roomView}>
<ErrorBoundary>
<RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo}
<RoomHeader
room={this.state.room}
searchInfo={searchInfo}
oobData={this.props.oobData}
inRoom={myMembership === 'join'}
collapsedRhs={collapsedRhs}

View file

@ -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 createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { KeyCode } from '../../Keyboard';
@ -166,6 +166,8 @@ module.exports = createReactClass({
}
this.resetScrollState();
this._itemlist = createRef();
},
componentDidMount: function() {
@ -328,7 +330,7 @@ module.exports = createReactClass({
this._isFilling = true;
}
const itemlist = this.refs.itemlist;
const itemlist = this._itemlist.current;
const firstTile = itemlist && itemlist.firstElementChild;
const contentTop = firstTile && firstTile.offsetTop;
const fillPromises = [];
@ -373,7 +375,7 @@ module.exports = createReactClass({
const origExcessHeight = excessHeight;
const tiles = this.refs.itemlist.children;
const tiles = this._itemlist.current.children;
// The scroll token of the first/last tile to be unpaginated
let markerScrollToken = null;
@ -602,7 +604,7 @@ module.exports = createReactClass({
const scrollNode = this._getScrollNode();
const viewportBottom = scrollNode.scrollHeight - (scrollNode.scrollTop + scrollNode.clientHeight);
const itemlist = this.refs.itemlist;
const itemlist = this._itemlist.current;
const messages = itemlist.children;
let node = null;
@ -644,7 +646,7 @@ module.exports = createReactClass({
const sn = this._getScrollNode();
sn.scrollTop = sn.scrollHeight;
} else if (scrollState.trackedScrollToken) {
const itemlist = this.refs.itemlist;
const itemlist = this._itemlist.current;
const trackedNode = this._getTrackedNode();
if (trackedNode) {
const newBottomOffset = this._topFromBottom(trackedNode);
@ -682,7 +684,7 @@ module.exports = createReactClass({
}
const sn = this._getScrollNode();
const itemlist = this.refs.itemlist;
const itemlist = this._itemlist.current;
const contentHeight = this._getMessagesHeight();
const minHeight = sn.clientHeight;
const height = Math.max(minHeight, contentHeight);
@ -724,7 +726,7 @@ module.exports = createReactClass({
if (!trackedNode || !trackedNode.parentElement) {
let node;
const messages = this.refs.itemlist.children;
const messages = this._itemlist.current.children;
const scrollToken = scrollState.trackedScrollToken;
for (let i = messages.length-1; i >= 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}>
<div className="mx_RoomView_messageListWrapper">
<ol ref="itemlist" className="mx_RoomView_MessageList" aria-live="polite">
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite">
{ this.props.children }
</ol>
</div>

View file

@ -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({
<input
key="searchfield"
type="text"
ref="search"
ref={this._search}
className={"mx_textinput_icon mx_textinput_search " + className}
value={ this.state.searchTerm }
onFocus={ this._onFocus }

View file

@ -19,7 +19,7 @@ limitations under the License.
import SettingsStore from "../../settings/SettingsStore";
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';
@ -203,6 +203,8 @@ const TimelinePanel = createReactClass({
this.lastRRSentEventId = undefined;
this.lastRMSentEventId = undefined;
this._messagePanel = createRef();
if (this.props.manageReadReceipts) {
this.updateReadReceiptOnUserActivity();
}
@ -425,8 +427,8 @@ const TimelinePanel = createReactClass({
if (payload.action === "edit_event") {
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
this.setState({editState}, () => {
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 (
<MessagePanel ref="messagePanel"
<MessagePanel
ref={this._messagePanel}
room={this.props.timelineSet.room}
permalinkCreator={this.props.permalinkCreator}
hidden={this.props.hidden}