{ room={this.state.room} resizing={this.state.resizing} waitForCall={isVideoRoom(this.state.room)} - skipLobby={this.context.roomViewStore.skipCallLobby() ?? false} + skipLobby={this.props.context.roomViewStore.skipCallLobby() ?? false} role="main" /> {previewBar} @@ -2593,15 +2600,15 @@ export class RoomView extends React.Component { onAppsClick = null; onForgetClick = null; onSearchClick = null; - if (this.state.room.canInvite(this.context.client.getSafeUserId())) { + if (this.state.room.canInvite(this.props.context.client.getSafeUserId())) { onInviteClick = this.onInviteClick; } viewingCall = true; } - const myMember = this.state.room!.getMember(this.context.client!.getSafeUserId()); + const myMember = this.state.room!.getMember(this.props.context.client!.getSafeUserId()); const showForgetButton = - !this.context.client.isGuest() && + !this.props.context.client.isGuest() && (([KnownMembership.Leave, KnownMembership.Ban] as Array).includes(myMembership) || myMember?.isKicked()); @@ -2665,4 +2672,6 @@ export class RoomView extends React.Component { } } -export default RoomView; +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index f24fa57d7d..1a65e70b4c 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, ReactNode } from "react"; +import React, { createRef, forwardRef, ReactNode } from "react"; import { Room } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "../../MatrixClientPeg"; @@ -56,6 +56,7 @@ import { shouldShowFeedback } from "../../utils/Feedback"; interface IProps { isPanelCollapsed: boolean; children?: ReactNode; + context: React.ContextType; } type PartialDOMRect = Pick; @@ -84,25 +85,21 @@ const below = (rect: PartialDOMRect): MenuProps => { }; }; -export default class UserMenu extends React.Component { - public static contextType = SDKContext; - public context!: React.ContextType; - +class UserMenu extends React.Component { private dispatcherRef?: string; private themeWatcherRef?: string; private readonly dndWatcherRef?: string; private buttonRef: React.RefObject = createRef(); - public constructor(props: IProps, context: React.ContextType) { - super(props, context); + public constructor(props: IProps) { + super(props); - this.context = context; this.state = { contextMenuPosition: null, isDarkTheme: this.isUserOnDarkTheme(), isHighContrast: this.isUserOnHighContrastTheme(), selectedSpace: SpaceStore.instance.activeSpaceRoom, - showLiveAvatarAddon: this.context.voiceBroadcastRecordingsStore.hasCurrent(), + showLiveAvatarAddon: this.props.context.voiceBroadcastRecordingsStore.hasCurrent(), }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); @@ -110,7 +107,7 @@ export default class UserMenu extends React.Component { } private get hasHomePage(): boolean { - return !!getHomePageUrl(SdkConfig.get(), this.context.client!); + return !!getHomePageUrl(SdkConfig.get(), this.props.context.client!); } private onCurrentVoiceBroadcastRecordingChanged = (recording: VoiceBroadcastRecording | null): void => { @@ -120,7 +117,7 @@ export default class UserMenu extends React.Component { }; public componentDidMount(): void { - this.context.voiceBroadcastRecordingsStore.on( + this.props.context.voiceBroadcastRecordingsStore.on( VoiceBroadcastRecordingsStoreEvent.CurrentChanged, this.onCurrentVoiceBroadcastRecordingChanged, ); @@ -134,7 +131,7 @@ export default class UserMenu extends React.Component { if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); - this.context.voiceBroadcastRecordingsStore.off( + this.props.context.voiceBroadcastRecordingsStore.off( VoiceBroadcastRecordingsStoreEvent.CurrentChanged, this.onCurrentVoiceBroadcastRecordingChanged, ); @@ -496,3 +493,7 @@ export default class UserMenu extends React.Component { ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx index f623ae7dcb..5fd1bb422b 100644 --- a/src/components/structures/auth/SoftLogout.tsx +++ b/src/components/structures/auth/SoftLogout.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ChangeEvent, SyntheticEvent } from "react"; +import React, { ChangeEvent, forwardRef, SyntheticEvent } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { Optional } from "matrix-events-sdk"; import { LoginFlow, MatrixError, SSOAction, SSOFlow } from "matrix-js-sdk/src/matrix"; @@ -60,6 +60,7 @@ interface IProps { // Called when the SSO login completes onTokenLoginCompleted: () => void; + context: React.ContextType; } interface IState { @@ -70,14 +71,9 @@ interface IState { flows: LoginFlow[]; } -export default class SoftLogout extends React.Component { - public static contextType = SDKContext; - public context!: React.ContextType; - - public constructor(props: IProps, context: React.ContextType) { - super(props, context); - - this.context = context; +class SoftLogout extends React.Component { + public constructor(props: IProps) { + super(props); this.state = { loginView: LoginView.Loading, @@ -104,7 +100,7 @@ export default class SoftLogout extends React.Component { if (!wipeData) return; logger.log("Clearing data from soft-logged-out session"); - Lifecycle.logout(this.context.oidcClientStore); + Lifecycle.logout(this.props.context.oidcClientStore); }, }); }; @@ -338,3 +334,7 @@ export default class SoftLogout extends React.Component { ); } } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index 828f9691da..a78c3da01d 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { forwardRef } from "react"; import { MatrixEvent, Room, @@ -66,6 +66,7 @@ interface IProps { searchQuery: string; onClose(): void; onSearchQueryChanged: (query: string) => void; + context: React.ContextType; } interface IState { @@ -77,18 +78,16 @@ interface IState { truncateAtInvited: number; } -export default class MemberList extends React.Component { +class MemberList extends React.Component { private readonly showPresence: boolean; private mounted = false; - public static contextType = SDKContext; - public context!: React.ContextType; private tiles: Map = new Map(); - public constructor(props: IProps, context: React.ContextType) { + public constructor(props: IProps) { super(props); this.state = this.getMembersState([], []); - this.showPresence = context?.memberListStore.isPresenceEnabled() ?? true; + this.showPresence = props.context?.memberListStore.isPresenceEnabled() ?? true; this.mounted = true; this.listenForMembersChanges(); } @@ -218,7 +217,7 @@ export default class MemberList extends React.Component { if (showLoadingSpinner) { this.setState({ loading: true }); } - const { joined, invited } = await this.context.memberListStore.loadMemberList( + const { joined, invited } = await this.props.context.memberListStore.loadMemberList( this.props.roomId, this.props.searchQuery, ); @@ -449,3 +448,7 @@ export default class MemberList extends React.Component { inviteToRoom(room); }; } + +export default forwardRef>((props, ref) => ( + {(context) => } +)); diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index 3009c81a17..5cd96aad05 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; +import React, { forwardRef, ReactNode } from "react"; import { SERVICE_TYPES, HTTPError, IThreepid, ThreepidMedium } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; @@ -61,6 +61,7 @@ import { SDKContext } from "../../../../../contexts/SDKContext"; interface IProps { closeSettingsFn: () => void; + context: React.ContextType; } interface IState { @@ -92,17 +93,13 @@ interface IState { canMake3pidChanges: boolean; } -export default class GeneralUserSettingsTab extends React.Component { - public static contextType = SDKContext; - public context!: React.ContextType; - +class GeneralUserSettingsTab extends React.Component { private readonly dispatcherRef: string; - public constructor(props: IProps, context: React.ContextType) { + public constructor(props: IProps) { super(props); - this.context = context; - const cli = this.context.client!; + const cli = this.props.context.client!; this.state = { language: languageHandler.getCurrentLanguage(), @@ -151,7 +148,7 @@ export default class GeneralUserSettingsTab extends React.Component { if (payload.action === "id_server_changed") { - this.setState({ haveIdServer: Boolean(this.context.client!.getIdentityServerUrl()) }); + this.setState({ haveIdServer: Boolean(this.props.context.client!.getIdentityServerUrl()) }); this.getThreepidState(); } }; @@ -165,7 +162,7 @@ export default class GeneralUserSettingsTab extends React.Component { - const cli = this.context.client!; + const cli = this.props.context.client!; const capabilities = await cli.getCapabilities(); // this is cached const changePasswordCap = capabilities["m.change_password"]; @@ -175,8 +172,8 @@ export default class GeneralUserSettingsTab extends React.Component { - const cli = this.context.client!; + const cli = this.props.context.client!; // Check to see if terms need accepting this.checkTerms(); @@ -213,7 +210,7 @@ export default class GeneralUserSettingsTab extends React.Component { // By starting the terms flow we get the logic for checking which terms the user has signed // for free. So we might as well use that for our own purposes. - const idServerUrl = this.context.client!.getIdentityServerUrl(); + const idServerUrl = this.props.context.client!.getIdentityServerUrl(); if (!this.state.haveIdServer || !idServerUrl) { this.setState({ idServerHasUnsignedTerms: false }); return; @@ -223,7 +220,7 @@ export default class GeneralUserSettingsTab extends React.Component { return new Promise((resolve, reject) => { @@ -573,3 +570,9 @@ export default class GeneralUserSettingsTab extends React.Component>((props, ref) => ( + + {(context) => } + +)); diff --git a/test/components/structures/RoomView-test.tsx b/test/components/structures/RoomView-test.tsx index 31f5c896ae..166c11f5b5 100644 --- a/test/components/structures/RoomView-test.tsx +++ b/test/components/structures/RoomView-test.tsx @@ -53,7 +53,7 @@ import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { Action } from "../../../src/dispatcher/actions"; import dis, { defaultDispatcher } from "../../../src/dispatcher/dispatcher"; import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload"; -import { RoomView as _RoomView } from "../../../src/components/structures/RoomView"; +import _RoomView from "../../../src/components/structures/RoomView"; import ResizeNotifier from "../../../src/utils/ResizeNotifier"; import SettingsStore from "../../../src/settings/SettingsStore"; import { SettingLevel } from "../../../src/settings/SettingLevel"; @@ -112,7 +112,7 @@ describe("RoomView", () => { jest.clearAllMocks(); }); - const mountRoomView = async (ref?: RefObject<_RoomView>): Promise => { + const mountRoomView = async (ref?: RefObject>): Promise => { if (stores.roomViewStore.getRoomId() !== room.roomId) { const switchedRoom = new Promise((resolve) => { const subFn = () => { @@ -185,8 +185,8 @@ describe("RoomView", () => { await flushPromises(); return roomView; }; - const getRoomViewInstance = async (): Promise<_RoomView> => { - const ref = createRef<_RoomView>(); + const getRoomViewInstance = async (): Promise> => { + const ref = createRef>(); await mountRoomView(ref); return ref.current!; }; @@ -197,7 +197,7 @@ describe("RoomView", () => { }); describe("when there is an old room", () => { - let instance: _RoomView; + let instance: React.ComponentRef; let oldRoom: Room; beforeEach(async () => { @@ -596,7 +596,7 @@ describe("RoomView", () => { const eventMapper = (obj: Partial) => new MatrixEvent(obj); - const roomViewRef = createRef<_RoomView>(); + const roomViewRef = createRef>(); const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef); // @ts-ignore - triggering a search organically is a lot of work roomViewRef.current!.setState({ @@ -657,7 +657,7 @@ describe("RoomView", () => { const eventMapper = (obj: Partial) => new MatrixEvent(obj); - const roomViewRef = createRef<_RoomView>(); + const roomViewRef = createRef>(); const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef); // @ts-ignore - triggering a search organically is a lot of work roomViewRef.current!.setState({ diff --git a/test/components/views/rooms/MemberList-test.tsx b/test/components/views/rooms/MemberList-test.tsx index 3b7c5a13bd..dbf5b270df 100644 --- a/test/components/views/rooms/MemberList-test.tsx +++ b/test/components/views/rooms/MemberList-test.tsx @@ -59,7 +59,7 @@ describe("MemberList", () => { let client: MatrixClient; let root: RenderResult; let memberListRoom: Room; - let memberList: MemberList; + let memberList: React.ComponentRef; let adminUsers: RoomMember[] = []; let moderatorUsers: RoomMember[] = []; @@ -214,7 +214,7 @@ describe("MemberList", () => { memberListRoom.currentState.members[member.userId] = member; } - const gatherWrappedRef = (r: MemberList) => { + const gatherWrappedRef = (r: React.ComponentRef) => { memberList = r; }; const context = new TestSdkContext();