Enable react-compiler eslint to spot antipatterns (#28652)

* Switch to React18 useId

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Enable react-compiler eslint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix an easy one

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Disable in tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix usage of useRef as memoization

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix mutation of external values in hooks

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Make React compiler happy about some frankly non-issues

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix MapMock

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Revert MemberListViewModel.tsx changes and disable linter per line

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Make viewmodel compatible with react-compiler linter

- Remove searchQuery ref/state and instead pass this query to the
  loadMember function.
- Now we no longer need a separate search function

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: R Midhun Suresh <hi@midhun.dev>
This commit is contained in:
Michael Telatynski
2025-01-16 12:26:00 +00:00
committed by GitHub
parent e5ca7954c8
commit ef1597ff2d
35 changed files with 146 additions and 136 deletions

View File

@@ -58,11 +58,10 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
if (canvas) canvas.height = UIStore.instance.windowHeight;
UIStore.instance.on(UI_EVENTS.Resize, resize);
const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored
return () => {
dis.unregister(dispatcherRef);
UIStore.instance.off(UI_EVENTS.Resize, resize);
// eslint-disable-next-line react-hooks/exhaustive-deps
const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored
for (const effect in currentEffects) {
const effectModule: ICanvasEffect = currentEffects.get(effect)!;
if (effectModule && effectModule.isRunning) {

View File

@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import React, { RefObject } from "react";
import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
interface IProps {
sensor: Element;
sensor: RefObject<Element>;
breakpoint: number;
onMeasurement(narrow: boolean): void;
}
@@ -35,14 +35,14 @@ export default class Measured extends React.PureComponent<IProps> {
}
public componentDidUpdate(prevProps: Readonly<IProps>): void {
const previous = prevProps.sensor;
const current = this.props.sensor;
const previous = prevProps.sensor.current;
const current = this.props.sensor.current;
if (previous === current) return;
if (previous) {
UIStore.instance.stopTrackingElementDimensions(`Measured${this.instanceId}`);
}
if (current) {
UIStore.instance.trackElementDimensions(`Measured${this.instanceId}`, this.props.sensor);
UIStore.instance.trackElementDimensions(`Measured${this.instanceId}`, this.props.sensor.current);
}
}

View File

@@ -213,7 +213,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
header={_t("right_panel|video_room_chat|title")}
ref={this.card}
>
{this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />}
<Measured sensor={this.card} onMeasurement={this.onMeasurement} />
<div className="mx_TimelineCard_timeline">
{jumpToBottom}
<TimelinePanel

View File

@@ -109,6 +109,7 @@ export function ReadReceiptGroup({
readReceiptPosition = readReceiptMap[userId];
if (!readReceiptPosition) {
readReceiptPosition = {};
// eslint-disable-next-line react-compiler/react-compiler
readReceiptMap[userId] = readReceiptPosition;
}
}

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { useCallback, useRef, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { EventType, KnownMembership, MatrixEvent, Room, RoomStateEvent, RoomMember } from "matrix-js-sdk/src/matrix";
import { CryptoApi, CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import { logger } from "matrix-js-sdk/src/logger";
@@ -213,9 +213,11 @@ export const UserIdentityWarning: React.FC<UserIdentityWarningProps> = ({ room }
initialisedRef.current = InitialisationStatus.Completed;
}, [crypto, room, addMembersWhoNeedApproval, updateCurrentPrompt]);
loadMembers().catch((e) => {
logger.error("Error initialising UserIdentityWarning:", e);
});
useEffect(() => {
loadMembers().catch((e) => {
logger.error("Error initialising UserIdentityWarning:", e);
});
}, [loadMembers]);
// When a user's verification status changes, we check if they need to be
// added/removed from the set of members needing approval.

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { ForwardedRef, forwardRef, MutableRefObject, useRef } from "react";
import React, { ForwardedRef, forwardRef, MutableRefObject, useMemo } from "react";
import classNames from "classnames";
import EditorStateTransfer from "../../../../utils/EditorStateTransfer";
@@ -44,7 +44,7 @@ export default function EditWysiwygComposer({
className,
...props
}: EditWysiwygComposerProps): JSX.Element {
const defaultContextValue = useRef(getDefaultContextValue({ editorStateTransfer }));
const defaultContextValue = useMemo(() => getDefaultContextValue({ editorStateTransfer }), [editorStateTransfer]);
const initialContent = useInitialContent(editorStateTransfer);
const isReady = !editorStateTransfer || initialContent !== undefined;
@@ -55,7 +55,7 @@ export default function EditWysiwygComposer({
}
return (
<ComposerContext.Provider value={defaultContextValue.current}>
<ComposerContext.Provider value={defaultContextValue}>
<WysiwygComposer
className={classNames("mx_EditWysiwygComposer", className)}
initialContent={initialContent}

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { ForwardedRef, forwardRef, MutableRefObject, useRef } from "react";
import React, { ForwardedRef, forwardRef, MutableRefObject, useMemo } from "react";
import { IEventRelation } from "matrix-js-sdk/src/matrix";
import { useWysiwygSendActionHandler } from "./hooks/useWysiwygSendActionHandler";
@@ -52,10 +52,13 @@ export default function SendWysiwygComposer({
...props
}: SendWysiwygComposerProps): JSX.Element {
const Composer = isRichTextEnabled ? WysiwygComposer : PlainTextComposer;
const defaultContextValue = useRef(getDefaultContextValue({ eventRelation: props.eventRelation }));
const defaultContextValue = useMemo(
() => getDefaultContextValue({ eventRelation: props.eventRelation }),
[props.eventRelation],
);
return (
<ComposerContext.Provider value={defaultContextValue.current}>
<ComposerContext.Provider value={defaultContextValue}>
<Composer
className="mx_SendWysiwygComposer"
leftComponent={e2eStatus && <E2EIcon status={e2eStatus} />}

View File

@@ -21,6 +21,7 @@ export function useComposerFunctions(
() => ({
clear: () => {
if (ref.current) {
// eslint-disable-next-line react-compiler/react-compiler
ref.current.innerHTML = "";
}
},

View File

@@ -12,6 +12,7 @@ export function usePlainTextInitialization(initialContent = "", ref: RefObject<H
useEffect(() => {
// always read and write the ref.current using .innerHTML for consistency in linebreak and HTML entity handling
if (ref.current) {
// eslint-disable-next-line react-compiler/react-compiler
ref.current.innerHTML = initialContent;
}
}, [ref, initialContent]);

View File

@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
import React, { ChangeEvent, JSX, useCallback, useMemo, useRef, useState } from "react";
import React, { ChangeEvent, JSX, useCallback, useMemo, useState } from "react";
import {
InlineField,
ToggleControl,
@@ -39,12 +39,12 @@ import { useSettingValue } from "../../../hooks/useSettings";
*/
export function ThemeChoicePanel(): JSX.Element {
const themeState = useTheme();
const themeWatcher = useRef(new ThemeWatcher());
const themeWatcher = useMemo(() => new ThemeWatcher(), []);
const customThemeEnabled = useSettingValue("feature_custom_themes");
return (
<SettingsSubsection heading={_t("common|theme")} legacy={false} data-testid="themePanel">
{themeWatcher.current.isSystemThemeSupported() && (
{themeWatcher.isSystemThemeSupported() && (
<SystemTheme systemThemeActivated={themeState.systemThemeActivated} />
)}
<ThemeSelectors theme={themeState.theme} disabled={themeState.systemThemeActivated} />

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { useCallback, useMemo, useState } from "react";
import { JoinRule, EventType, RoomState, Room } from "matrix-js-sdk/src/matrix";
import { RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types";
import { _t } from "../../../../../languageHandler";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
@@ -24,48 +25,49 @@ interface ElementCallSwitchProps {
const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ room }) => {
const isPublic = useMemo(() => room.getJoinRule() === JoinRule.Public, [room]);
const [content, events, maySend] = useRoomState(
const [content, maySend] = useRoomState(
room,
useCallback(
(state: RoomState) => {
const content = state?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent();
const content = state
?.getStateEvents(EventType.RoomPowerLevels, "")
?.getContent<RoomPowerLevelsEventContent>();
return [
content ?? {},
content?.["events"] ?? {},
state?.maySendStateEvent(EventType.RoomPowerLevels, room.client.getSafeUserId()),
];
] as const;
},
[room.client],
),
);
const [elementCallEnabled, setElementCallEnabled] = useState<boolean>(() => {
return events[ElementCall.MEMBER_EVENT_TYPE.name] === 0;
return content.events?.[ElementCall.MEMBER_EVENT_TYPE.name] === 0;
});
const onChange = useCallback(
(enabled: boolean): void => {
setElementCallEnabled(enabled);
// Take a copy to avoid mutating the original
const newContent = { events: {}, ...content };
if (enabled) {
const userLevel = events[EventType.RoomMessage] ?? content.users_default ?? 0;
const userLevel = newContent.events[EventType.RoomMessage] ?? content.users_default ?? 0;
const moderatorLevel = content.kick ?? 50;
events[ElementCall.CALL_EVENT_TYPE.name] = isPublic ? moderatorLevel : userLevel;
events[ElementCall.MEMBER_EVENT_TYPE.name] = userLevel;
newContent.events[ElementCall.CALL_EVENT_TYPE.name] = isPublic ? moderatorLevel : userLevel;
newContent.events[ElementCall.MEMBER_EVENT_TYPE.name] = userLevel;
} else {
const adminLevel = events[EventType.RoomPowerLevels] ?? content.state_default ?? 100;
const adminLevel = newContent.events[EventType.RoomPowerLevels] ?? content.state_default ?? 100;
events[ElementCall.CALL_EVENT_TYPE.name] = adminLevel;
events[ElementCall.MEMBER_EVENT_TYPE.name] = adminLevel;
newContent.events[ElementCall.CALL_EVENT_TYPE.name] = adminLevel;
newContent.events[ElementCall.MEMBER_EVENT_TYPE.name] = adminLevel;
}
room.client.sendStateEvent(room.roomId, EventType.RoomPowerLevels, {
events: events,
...content,
});
room.client.sendStateEvent(room.roomId, EventType.RoomPowerLevels, newContent);
},
[room.client, room.roomId, content, events, isPublic],
[room.client, room.roomId, content, isPublic],
);
const brand = SdkConfig.get("element_call").brand ?? DEFAULTS.element_call.brand;

View File

@@ -27,7 +27,7 @@ type Props = {
const MATCH_SYSTEM_THEME_ID = "MATCH_SYSTEM_THEME_ID";
const QuickThemeSwitcher: React.FC<Props> = ({ requestClose }) => {
const orderedThemes = useMemo(getOrderedThemes, []);
const orderedThemes = useMemo(() => getOrderedThemes(), []);
const themeState = useTheme();
const nonHighContrast = findNonHighContrastTheme(themeState.theme);

View File

@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ClientEvent, MatrixClient, MatrixEventEvent, Room } from "matrix-js-sdk/src/matrix";
import { throttle } from "lodash";
@@ -42,14 +42,12 @@ export function useUnreadThreadRooms(forceComputation: boolean): Result {
setResult(computeUnreadThreadRooms(mxClient, msc3946ProcessDynamicPredecessor, settingTACOnlyNotifs));
}, [mxClient, msc3946ProcessDynamicPredecessor, settingTACOnlyNotifs]);
// The exhautive deps lint rule can't compute dependencies here since it's not a plain inline func.
// We make this as simple as possible so its only dep is doUpdate itself.
// eslint-disable-next-line react-hooks/exhaustive-deps
const scheduleUpdate = useCallback(
throttle(doUpdate, MIN_UPDATE_INTERVAL_MS, {
leading: false,
trailing: true,
}),
const scheduleUpdate = useMemo(
() =>
throttle(doUpdate, MIN_UPDATE_INTERVAL_MS, {
leading: false,
trailing: true,
}),
[doUpdate],
);