-
-
Notifications
You must be signed in to change notification settings - Fork 834
Remove legacy consumers of the React contexts in favour of HOCs #12444
Conversation
Signed-off-by: Michael Telatynski <[email protected]>
Signed-off-by: Michael Telatynski <[email protected]>
Signed-off-by: Michael Telatynski <[email protected]>
Signed-off-by: Michael Telatynski <[email protected]>
Signed-off-by: Michael Telatynski <[email protected]>
Signed-off-by: Michael Telatynski <[email protected]>
Signed-off-by: Michael Telatynski <[email protected]>
For external readers, this is "Upgrade to react 18" |
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.
So many questions.
- WTF is a HOC, and why is it better than a Context object? (There doesn't seem to be any documentation at all on
matrixHOC
orwithMatrixClientHOC
; I'd expect that to be a pre-requisite for this sort of large-scale migration). - If we're getting rid of
MatrixClientContext
, we should probably update the documentation on it to say that it's deprecated. - Can we do this in a way that doesn't involve changing and reviewing 96 files at once? Sorry, but it's extremely unlikely I'm going to have time to review this as it stands.
More questions:
|
Oh, stupid question, I think. |
https://legacy.reactjs.org/docs/higher-order-components.html
It is a Context object, just passed like a normal prop with normal types rather than the mess that is
It is an alternative path towards React 18 which isn't blocked on a decision between Typescript & Babel to land.
This is a manner of consuming a context, like
We're not. We're just not going to consume it via the really limited class component
We can split it into the constituent commits, are they not good enough here? |
... which says: Higher-order components are not commonly used in modern React code. But that's by the by. Anyway, please could the HOC types be documented rather than relying on institutional knowledge?
Ok, fair enough, but probably we could helpfully write in
I'm afraid not, really. I don't want to have to plough through this massive PR, whether it's broken into commits or not. |
There's no reason to use |
Please, write that down in the documentation then. |
Signed-off-by: Michael Telatynski <[email protected]>
… t3chguy/react18/context-hocs
* A higher order component that injects the MatrixClient into props.mxClient of the wrapped component. | ||
* Preferred over using `static contextType` as the types for this are quite broken in React 17. | ||
* Inherently no different to wrapping in MatrixClientContext.Consumer but saves a lot of boilerplate. | ||
* @param ComposedComponent the ComponentClass you wish to wrap in the HOC | ||
* @returns a new component that takes the same props as the original component, but with an additional mxClient prop |
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.
* A higher order component that injects the MatrixClient into props.mxClient of the wrapped component. | |
* Preferred over using `static contextType` as the types for this are quite broken in React 17. | |
* Inherently no different to wrapping in MatrixClientContext.Consumer but saves a lot of boilerplate. | |
* @param ComposedComponent the ComponentClass you wish to wrap in the HOC | |
* @returns a new component that takes the same props as the original component, but with an additional mxClient prop | |
* A higher order component that injects the `MatrixClient` into `props.mxClient` of the wrapped component. | |
* | |
* Preferred over using a [static `contextType` property](https://react.dev/reference/react/Component#static-contexttype) | |
* as the types for that are quite broken in React 17. | |
* Inherently, no different to wrapping in `MatrixClientContext.Consumer` but saves a lot of boilerplate. | |
* | |
* @param ComposedComponent - the `ComponentClass` you wish to wrap in the HOC | |
* @returns a new component class that takes the same props as the original component, but with an additional `mxClient` prop |
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.
takes the same props as the original component, but with an additional
mxClient
prop
This doesn't sound right. The original prop must take an mxClient
prop, which we will populate from the context. The returned component omits the mxClient
prop.
import AutoHideScrollbar from "./AutoHideScrollbar"; | ||
import { ActionPayload } from "../../dispatcher/payloads"; | ||
|
||
interface IProps { | ||
interface IProps extends MatrixClientProps { |
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.
honestly, given MatrixClientProps
contains exactly one entry, I'd find it much clearer to spell it out explicitly, and it's hard to see much disadvantage to doing so. (We can't exactly just rename it in MatrixClientProps
, because we rely on it being called mxClient` below.
Not a strong opinion though, and maybe not worth changing now.
@@ -132,7 +131,7 @@ class LoggedInView extends React.Component<IProps, IState> { | |||
public static displayName = "LoggedInView"; | |||
|
|||
protected readonly _matrixClient: MatrixClient; | |||
protected readonly _roomView: React.RefObject<RoomViewType>; | |||
protected readonly _roomView: React.RefObject<React.ComponentRef<typeof RoomView>>; |
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 couldn't find any documentation for React.ComponentRef
. Can you explain what it does, and why this change is needed?
/** | ||
* Holds on to an event, caching the information about it in the context of the current messages list. | ||
* Holds on to an event, caching the information about it in the props.context. of the current messages list. |
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 far from convinced this is accurate, but let's not make it more misleading.
* Holds on to an event, caching the information about it in the props.context. of the current messages list. | |
* Holds on to an event, caching the information about it in the context of the current messages list. |
* Avoids calling shouldShowEvent more times than we need to. | ||
* Simplifies threading of event context like whether it's the last successful event we sent which cannot be determined | ||
* Simplifies threading of event props.context. like whether it's the last successful event we sent which cannot be determined |
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 think you've been too gung-ho with search and replace.
* Simplifies threading of event props.context. like whether it's the last successful event we sent which cannot be determined | |
* Simplifies threading of event context like whether it's the last successful event we sent which cannot be determined |
@@ -45,6 +45,7 @@ interface IProps { | |||
roomId: string; | |||
onClose: () => void; | |||
resizeNotifier: ResizeNotifier; | |||
context: React.ContextType<typeof RoomContext>; |
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've been dithering about whether I want you to document these, and I've decided that I do, if only to tell callers that they don't need to worry about them.
You could argue that FilePanel
(as defined on line 58, not as exported by FilePanel.ts
🤯) is not a public component, and therefore is not subject to the rules of the coding style doc that say "properties must be documented", but I think that is not in keeping with the spirit of the coding style doc. The point is that if I want to use a FilePanel
, this is going to be my reference to the properties I need to provide, and I don't want to have to reverse-engineer the forwardRef
incantation to realise that I don't need to specify a context
here.
context: React.ContextType<typeof RoomContext>; | |
/** Details of the room in which this file belongs. Populated automatically from the `RoomContext`. */ | |
context: React.ContextType<typeof RoomContext>; |
Likewise throughout.
This also ties into making the mxClient
properties explicit rather than inheriting from MatrixClientProps
: it gives a place to document the fact that callers don't need to populate mxClient
provided there is a MatrixClientContext
.
@@ -312,4 +311,6 @@ class FilePanel extends React.Component<IProps, IState> { | |||
} | |||
} | |||
|
|||
export default FilePanel; | |||
export default forwardRef<FilePanel, Omit<IProps, "context">>((props, ref) => ( |
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.
Likewise, a word of documentation on this would be useful.
export default forwardRef<FilePanel, Omit<IProps, "context">>((props, ref) => ( | |
/** Wraps a `FilePanel`, populating its `context` property from the current {@link RoomContext}. */ | |
export default forwardRef<FilePanel, Omit<IProps, "context">>((props, ref) => ( |
Likewise throughout.
@@ -123,7 +123,7 @@ interface IState { | |||
|
|||
export default class BasicMessageEditor extends React.Component<IProps, IState> { | |||
public readonly editorRef = createRef<HTMLDivElement>(); | |||
private autocompleteRef = createRef<Autocomplete>(); | |||
private autocompleteRef = createRef<React.ComponentRef<typeof Autocomplete>>(); |
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.
what's the difference here?
@@ -122,28 +122,25 @@ export function createEditContent( | |||
interface IEditMessageComposerProps extends MatrixClientProps { | |||
editState: EditorStateTransfer; | |||
className?: string; | |||
context: React.ContextType<typeof RoomContext>; |
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.
got to say, calling all these things just context
isn't the clearest thing, especially in cases like this where there are actually two contexts in play. roomContext
would be a better name.
Not a showstopper though.
export default forwardRef<ReplyPreview, Omit<IProps, "context">>((props, ref) => ( | ||
<RoomContext.Consumer>{(context) => <ReplyPreview {...props} context={context} ref={ref} />}</RoomContext.Consumer> | ||
)); |
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.
How come MatrixClientContext qualifies for a HOC to do this boilerplate, but the 15000 <RoomContext.Consumer>
s don't, by the way?
I'm sorry that I've been sitting on this for a week, but also: This makes it really hard to fit a review in around other work. I'm much more likely to be able to do smaller reviews promptly; a review of this size, changing this many different things, requires several hours of uninterrupted time to review properly. Part of the blame is on me here for not rejecting it sooner, but I really feel like it's the author's job to make things easy for the reviewer without having to be asked; it shouldn't be on the reviewer to have to decide where to draw a line. Please do try to make PRs smaller. |
Thanks for the review. Don't have that much time to spend on this anymore. Will close it until someone from the wysiwyg or compound teams can resume it for their benefit. |
For https://github.com/element-hq/wat-internal/issues/65
Closes #10311