chore: resolve mechanical react-hooks/exhaustive-deps warnings (#12207)

Adds missing stable dependencies (e.g. `t`, prop callbacks, store refs,
`setFocusedCommentId`) and removes unnecessary ones across hooks where the
fix is straightforward. For the two MobX-observed `.orderedData` deps in
`History.tsx`, keeps the original deps and silences the false positive
with `eslint-disable-next-line` so the memos still recompute when the
underlying observable arrays change.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Tom Moor
2026-04-28 22:06:09 -04:00
committed by GitHub
parent 4c85c4d08d
commit f8e70c2c39
23 changed files with 74 additions and 29 deletions
+8 -1
View File
@@ -146,7 +146,14 @@ function Collaborators(props: Props) {
/>
);
},
[presentIds, editingIds, observingUserId, currentUserId, handleAvatarClick]
[
presentIds,
editingIds,
observingUserId,
currentUserId,
handleAvatarClick,
t,
]
);
if (!document.insightsEnabled) {
+2 -2
View File
@@ -99,7 +99,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
);
}, 2000);
onFileUploadStart?.();
}, [onFileUploadStart, dictionary.uploadingWithProgress]);
}, [onFileUploadStart, dictionary]);
const handleFileUploadProgress = React.useCallback(
(fileId: string, fractionComplete: number) => {
@@ -118,7 +118,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
});
}
},
[dictionary.uploadingWithProgress]
[dictionary]
);
const handleFileUploadStop = React.useCallback(() => {
@@ -40,7 +40,7 @@ export function ShareSubscribeForm({
setStatus("error");
}
},
[shareId, documentId, email]
[shareId, documentId, email, t]
);
const handleChange = useCallback(
+2 -2
View File
@@ -83,7 +83,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function Sidebar_(
ui.set({ sidebarWidth: Math.max(newWidth, minWidth) });
}
},
[ui, theme, offset, minWidth, maxWidth, direction]
[ui, theme, offset, minWidth, maxWidth, direction, canCollapse]
);
const handleStopDrag = React.useCallback(() => {
@@ -107,7 +107,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function Sidebar_(
} else {
ui.set({ sidebarWidth: width });
}
}, [ui, isSmallerThanMinimum, minWidth, width]);
}, [ui, isSmallerThanMinimum, minWidth, width, canCollapse]);
const handleBlur = React.useCallback(() => {
setHovering(false);
@@ -49,13 +49,13 @@ function CollectionLinkChildren({
if (!expanded) {
setShowing(pageSize);
}
}, [expanded]);
}, [expanded, pageSize]);
const showMore = useCallback(() => {
if (childDocuments && childDocuments.length > showing) {
setShowing((value) => value + pageSize);
}
}, [childDocuments, showing]);
}, [childDocuments, showing, pageSize]);
return (
<Folder expanded={expanded}>
@@ -70,7 +70,7 @@ function SharedWithMeLink({ membership, depth = 0 }: Props) {
void documents.fetch(documentId);
void membership.fetchDocuments();
}
}, [documentId, documents]);
}, [documentId, documents, membership]);
React.useEffect(() => {
if (isActiveDocument && membership.documentId) {
@@ -132,7 +132,7 @@ function SidebarLink(
onClick(ev);
}
},
[onClick, disabled, expanded]
[onClick, disabled]
);
const handleDisclosureClick = React.useCallback(
+1 -1
View File
@@ -102,7 +102,7 @@ const LinkEditor: React.FC<Props> = ({
const openLink = React.useCallback(() => {
commands["openLink"]();
}, []);
}, [commands]);
const removeLink = React.useCallback(() => {
commands["removeLink"]();
+10 -1
View File
@@ -166,7 +166,16 @@ export function MediaDimension() {
height: finalHeight,
});
}
}, [commands, width, height, localDimension, nodeType, error, reset]);
}, [
commands,
width,
height,
localDimension,
nodeType,
error,
reset,
isOutsideBounds,
]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
+3 -3
View File
@@ -61,7 +61,7 @@ export function MediaLinkEditor({
const { state, dispatch } = view;
dispatch(state.tr.deleteSelection());
onLinkRemove();
}, [view]);
}, [view, onLinkRemove]);
const update = useCallback(() => {
const { state } = view;
@@ -74,7 +74,7 @@ export function MediaLinkEditor({
view.dispatch(tr);
moveSelectionToEnd();
onLinkUpdate();
}, [localUrl, node, view, moveSelectionToEnd]);
}, [localUrl, node, view, moveSelectionToEnd, onLinkUpdate]);
useOnClickOutside(wrapperRef, onClickOutside);
@@ -99,7 +99,7 @@ export function MediaLinkEditor({
}
}
},
[update, moveSelectionToEnd]
[update, moveSelectionToEnd, onEscape]
);
if (!node) {
+1 -1
View File
@@ -69,7 +69,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
res.data.collections.map(collections.add);
res.data.groups.map(groups.add);
});
}, [search, documents, users, collections])
}, [search, documents, users, collections, groups, maxResultsInSection])
);
useEffect(() => {
+1 -1
View File
@@ -187,7 +187,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
selection.to
)
);
}, [props.search, props.trigger, view]);
}, [props.search, props.trigger, view, isMobile]);
const restoreSelection = React.useCallback(() => {
if (!isMobile) {
+1 -1
View File
@@ -65,7 +65,7 @@ export function useCollectionMenuAction({ collectionId, onRename }: Props) {
ActionSeparator,
deleteCollection,
],
[t, can.createDocument, can.update, onRename]
[t, can.update, onRename]
);
return useMenuAction(actions);
@@ -158,7 +158,7 @@ function CommentThreadItem({
setFocusedCommentId(null);
}
},
[comment.id, onUpdate]
[comment.id, onUpdate, setFocusedCommentId]
);
const handleDelete = React.useCallback(() => {
@@ -222,6 +222,7 @@ function DataLoader({ match, children }: Props) {
shares,
ui,
revisionId,
missingPolicy,
]);
// Auto-enter presentation mode when ?present=true query param is set
+2 -2
View File
@@ -103,7 +103,7 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
}
ui.set({ rightSidebar: "comments" });
}
}, [focusedComment, ui, document.id, params]);
}, [focusedComment, ui, document.id, params, setFocusedCommentId]);
// Save document when blurring title, but delay so that if clicking on a
// button this is allowed to execute first.
@@ -148,7 +148,7 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
setFocusedCommentId(commentId);
}
},
[comments, user?.id, props.id]
[comments, user?.id, props.id, setFocusedCommentId]
);
// Soft delete the Comment model when associated mark is totally removed.
@@ -96,7 +96,7 @@ function History() {
updateLocation({ changes: null, compareTo: null });
}
},
[updateLocation]
[updateLocation, setDefaultShowChanges]
);
const selectedRevisionId = historyMatch?.params.revisionId;
@@ -127,7 +127,7 @@ function History() {
if (defaultShowChanges) {
updateLocation({ changes: "true" });
}
}, [defaultShowChanges]);
}, [defaultShowChanges, updateLocation]);
const fetchHistory = React.useCallback(async () => {
if (!document) {
@@ -170,6 +170,7 @@ function History() {
.getByDocumentId(document.id)
.filter((revision: Revision) => revision.id !== latestRevisionId)
.slice(0, revisionsOffset);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [document, revisions.orderedData, revisionsOffset]);
const nonRevisionEvents = React.useMemo(
@@ -177,6 +178,7 @@ function History() {
document
? events.getByDocumentId(document.id).slice(0, eventsOffset)
: [],
// eslint-disable-next-line react-hooks/exhaustive-deps
[document, events.orderedData, eventsOffset]
);
@@ -228,7 +230,7 @@ function History() {
} else {
history.goBack();
}
}, [history, document, sidebarContext]);
}, [history, document, sidebarContext, isMobile]);
useKeyDown("Escape", onCloseHistory);
+10 -1
View File
@@ -69,7 +69,16 @@ function DocumentDelete({ document, onSubmit }: Props) {
setDeleting(false);
}
},
[onSubmit, ui, document, documents, history, collection]
[
onSubmit,
ui,
document,
documents,
history,
collection,
userMemberships,
groupMemberships,
]
);
const handleArchive = React.useCallback(
@@ -177,6 +177,7 @@ export const GroupMembersTable = observer(function GroupMembersTable({
t,
can.update,
group.id,
group.isExternallyManaged,
groupUsers.orderedData,
permissions,
handlePermissionChange,
@@ -146,7 +146,7 @@ export function SharesTable({ data, canManage, ...rest }: Props) {
}
: undefined,
]),
[t, hasDomain, canManage]
[t, hasDomain, canManage, formatNumber]
);
return (
+9 -1
View File
@@ -55,7 +55,15 @@ export const Notion = observer(() => {
onClose: clearQueryParams,
});
}
}, [t, dialogs, oauthSuccess, service, clearQueryParams]);
}, [
t,
dialogs,
oauthSuccess,
service,
clearQueryParams,
handleSubmit,
integrationId,
]);
React.useEffect(() => {
if (!oauthError) {
@@ -53,7 +53,15 @@ export function ImportDialog({ integrationId, onSubmit }: Props) {
toast.error(err.message);
resetSubmitting();
}
}, [permission, onSubmit]);
}, [
permission,
onSubmit,
integrationId,
t,
imports,
resetSubmitting,
setSubmitting,
]);
return (
<Flex column gap={12}>
+1 -1
View File
@@ -239,7 +239,7 @@ export const MentionURL = (props: IssueUrlProps) => {
};
void fetchUnfurl();
}, [unfurls, url, node, isMounted]);
}, [unfurls, url, node, isMounted, onChangeUnfurl]);
if (!unfurl) {
return !loaded ? (