-
-
Notifications
You must be signed in to change notification settings - Fork 828
Move subscriptions out of RoomView constructor #9973
Conversation
7d574ef
to
55b88c0
Compare
55b88c0
to
de1b4c6
Compare
de1b4c6
to
37640df
Compare
37640df
to
85f70ea
Compare
Fix a couple issues with the RoomView tests: * Many tests were rendering with no room membership, resulting in just the "do you want to join?" prompt. * Some tests were erroring, which was caught by error boundaries. * Some tests were still using enzyme. Those have been migrated to react-testing-library. Signed-off-by: Clark Fischer <[email protected]>
Signed-off-by: Clark Fischer <[email protected]>
Signed-off-by: Clark Fischer <[email protected]>
Components are not 'cleaned up' (i.e. `componentWillUnmount` called) unless `componentDidMount` was called. React doesn't expect to have to simulate an unmount if the component was never mounted to begin with. In the case of RoomView, that means that the subscriptions created in the constructor are never removed, i.e. a leak. This moves the subscriptions to the `componentDidMount` method. See element-hq/element-web#24236 (comment) Signed-off-by: Clark Fischer <[email protected]>
85f70ea
to
24f8f9f
Compare
Thanks for you PR @clarkf . Do you think it is possible to raise the test coverage for changed code to at least 80 %? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am stopping my review somewhere in the middle. Moving the subscriptions out of the constructor makes sense 👍 But this PR also targets fixing all the type issues in RoomView
.
I would suggest to split this up: One PR for the type fixes and another to move out the subscriptions.
For the type fixes: In many places just adding a !
to silence the linter is probably not what we should do. Instead the migration may contain more checks, if something is set and logging.
@@ -587,18 +540,18 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { | |||
// The rest will be lost for now, until the aggregation API on the server | |||
// becomes available to fetch a whole thread | |||
if (!initialEvent) { | |||
initialEvent = await fetchInitialEvent(this.context.client, roomId, initialEventId); | |||
initialEvent = (await fetchInitialEvent(client, roomId!, initialEventId)) || undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does a RoomView
work without a room Id? We should probably test if the room Id is set somewhere before instead of using !
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
roomId
has to be nullish (as does room
). It's a state variable, so it can't be known until the component mounts and the RoomViewStore
is checked. Immediately after the component mounts, roomId
is (basically) guaranteed to be defined, as long as the entire app is in a valid state. I agree that it's messy, but I think the scope of this PR is already an issue without more sweeping changes.
There is a test earlier in the method, it's just an ordering artifact I'll clear up.
|
||
const thread = initialEvent?.getThread(); | ||
if (thread && !initialEvent?.isThreadRoot) { | ||
dis.dispatch<ShowThreadPayload>({ | ||
action: Action.ShowThread, | ||
rootEvent: thread.rootEvent, | ||
rootEvent: thread.rootEvent!, // isThreadRoot guarantees |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logically yes. Technically it doesn't. Shouldn't initialEvent
be the root event?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar enough with the threads API to say. Maybe? There aren't any types here to make this work without some kind of assertion. I'll swap it, I was just trying to change as little as possible while adding context.
// Get the scroll state for the new room | ||
|
||
// If an event ID wasn't specified, default to the one saved for this room | ||
// in the scroll state store. Assume initialEventPixelOffset should be set. | ||
if (!newState.initialEventId) { | ||
const roomScrollState = RoomScrollStateStore.getScrollState(newState.roomId); | ||
const roomScrollState = RoomScrollStateStore!.getScrollState(newState.roomId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably use optional chaining here
const roomScrollState = RoomScrollStateStore!.getScrollState(newState.roomId); | |
const roomScrollState = RoomScrollStateStore?.getScrollState(newState.roomId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kind of a weird one. In reality, RoomScrollStateStore
is guaranteed to be defined:
matrix-react-sdk/src/stores/RoomScrollStateStore.ts
Lines 50 to 53 in e9d7232
if (window.mxRoomScrollStateStore === undefined) { | |
window.mxRoomScrollStateStore = new RoomScrollStateStore(); | |
} | |
export default window.mxRoomScrollStateStore; |
I believe this is a pre-TS artifact, which is why the default export is technically
RoomScrollStateStore | undefined
. Truthfully, the assertion belongs in that file (since it guarantees its presence).
Regarding the size/scope of this PR, I'm not sure how to proceed. The CI checks won't pass without the strict updates -- I've rewritten this patch set at least 4 times, one of which was an absolutely minimal change -- just the tests and moving the lines from one method to another. The I can separate the first three commits out into their own PR, but I'm not sure how much that would help. There'd just be two PR's.
I'll do another pass, looking for opportunities to modify the control flow instead of asserting. Silencing the typechecker was not my intent, in every(?) case, I verified that those things are supposed to be defined. My goal is not to change behavior or rewrite anything. |
@clarkf are you interested in continuing this PR now that the repository has been made to comply with |
Closing due to no response |
Motivation.
Submitting this separately from the other instances since it's... a bit hairy. Best reviewed one commit at a time, I reckon.
My objective is to move the subscription registration out of
RoomView
's constructor. React doesn't guarantee thatcomponentWillUmount
(i.e. 'the cleanup function') will be called unlesscomponentDidMount
was called. This means all those subscriptions were possibly not being cleaned up, i.e. leaking.In order to get there, I had to get
RoomView.ts
(at least) to pass the CI checks, namely the--noImplicitAny
and--strict
checks. Since #9940 is a thing, and in order to minimize conflicts, I pulled those changes pretty much directly. I had to touch a few signatures in other files, so CI will still probably complain.This may fix element-hq/element-web#10826
Checklist
This change is marked as an internal change (Task), so will not be included in the changelog.