Skip to content

Commit 544c8d1

Browse files
authored
fix: add useCallback() and useMemo() everywhere (#683)
1 parent 70e1e7b commit 544c8d1

14 files changed

Lines changed: 253 additions & 189 deletions

File tree

packages/root-cms/ui/components/DocEditor/DocEditor.tsx

Lines changed: 132 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {route} from 'preact-router';
4949
import * as schema from '../../../core/schema.js';
5050
import {useCollectionSchema} from '../../hooks/useCollectionSchema.js';
5151
import {
52+
buildDeeplinkUrl,
5253
DeeplinkProvider,
5354
scrollToDeeplink,
5455
useDeeplink,
@@ -265,19 +266,20 @@ DocEditor.Field = (props: FieldProps) => {
265266
const ref = useRef<HTMLDivElement>(null);
266267
const [value, setValue] = useState(null);
267268

268-
const fieldValueEmpty = testFieldEmpty(field, value);
269+
const fieldValueEmpty = useMemo(() => testFieldEmpty(field, value), [value]);
269270
const showTranslateIcon =
270271
(field.type === 'string' || field.type === 'richtext') && field.translate;
271272

272-
let showFieldHeader = !props.hideHeader && !field.hideLabel;
273-
// The "drawer" variant shows the header within the accordion button.
274-
if (field.type === 'object') {
275-
// Default to the "drawer" variant.
276-
const variant = field.variant || 'drawer';
277-
if (variant === 'drawer') {
278-
showFieldHeader = false;
273+
const showFieldHeader = useMemo(() => {
274+
if (field.type === 'object') {
275+
// Default to the "drawer" variant.
276+
const variant = field.variant || 'drawer';
277+
if (variant === 'drawer') {
278+
return false;
279+
}
279280
}
280-
}
281+
return !props.hideHeader && !field.hideLabel;
282+
}, [props.deepKey]);
281283

282284
useEffect(() => {
283285
const unsubscribe = props.draft.subscribe(
@@ -287,7 +289,7 @@ DocEditor.Field = (props: FieldProps) => {
287289
}
288290
);
289291
return unsubscribe;
290-
}, [props.draft, props.deepKey]);
292+
}, [props.deepKey]);
291293

292294
useEffect(() => {
293295
if (targeted) {
@@ -376,12 +378,6 @@ DocEditor.FieldHeader = (props: {
376378
/** Whether the field has translations to display (i.e. if the field is not empty). */
377379
hasTranslations?: boolean;
378380
}) => {
379-
function buildDeeplinkUrl() {
380-
const url = new URL(window.location.href);
381-
url.searchParams.set('deeplink', props.deepKey!);
382-
return url.toString();
383-
}
384-
385381
const deeplink = useDeeplink();
386382
const docData = useDocData() || {};
387383
const l10nSheet = docData.sys?.l10nSheet;
@@ -390,6 +386,11 @@ DocEditor.FieldHeader = (props: {
390386
const i18nLocales = i18nConfig.locales || ['en'];
391387
const editTranslationsModal = useEditTranslationsModal();
392388

389+
const deeplinkUrl = useMemo(
390+
() => buildDeeplinkUrl(props.deepKey || ''),
391+
[props.deepKey]
392+
);
393+
393394
return (
394395
<div className={joinClassNames(props.className, 'DocEditor__FieldHeader')}>
395396
{props.deprecated ? (
@@ -402,11 +403,11 @@ DocEditor.FieldHeader = (props: {
402403
{props.deepKey && (
403404
<a
404405
className="DocEditor__FieldHeader__label__deeplink"
405-
href={buildDeeplinkUrl()}
406+
href={deeplinkUrl}
406407
title="Link to field"
407408
onClick={(e) => {
408409
e.preventDefault();
409-
window.history.replaceState({}, '', buildDeeplinkUrl());
410+
window.history.replaceState({}, '', deeplinkUrl);
410411
deeplink.setValue(props.deepKey!);
411412
}}
412413
>
@@ -865,7 +866,7 @@ DocEditor.ArrayField = (props: FieldProps) => {
865866
}
866867
);
867868
return unsubscribe;
868-
}, [props.draft, props.deepKey]);
869+
}, [props.deepKey]);
869870

870871
// Focus the field that was just moved (for hotkey support).
871872
useEffect(() => {
@@ -878,89 +879,113 @@ DocEditor.ArrayField = (props: FieldProps) => {
878879
dispatch({type: 'add', draft: draft, deepKey: props.deepKey});
879880
};
880881

881-
const pasteBefore = (index: number) => {
882-
dispatch({
883-
type: 'pasteBefore',
884-
draft: draft,
885-
deepKey: props.deepKey,
886-
index: index,
887-
virtualClipboard: virtualClipboard,
888-
});
889-
};
890-
891-
const pasteAfter = (index: number) => {
892-
dispatch({
893-
type: 'pasteAfter',
894-
draft: draft,
895-
deepKey: props.deepKey,
896-
index: index,
897-
virtualClipboard: virtualClipboard,
898-
});
899-
};
900-
901-
const insertBefore = (index: number) => {
902-
dispatch({
903-
type: 'insertBefore',
904-
draft: draft,
905-
deepKey: props.deepKey,
906-
index: index,
907-
});
908-
};
909-
910-
const insertAfter = (index: number) => {
911-
dispatch({
912-
type: 'insertAfter',
913-
draft: draft,
914-
deepKey: props.deepKey,
915-
index: index,
916-
});
917-
};
882+
const pasteBefore = useCallback(
883+
(index: number) => {
884+
dispatch({
885+
type: 'pasteBefore',
886+
draft: draft,
887+
deepKey: props.deepKey,
888+
index: index,
889+
virtualClipboard: virtualClipboard,
890+
});
891+
},
892+
[props.deepKey]
893+
);
918894

919-
const duplicate = (index: number) => {
920-
dispatch({
921-
type: 'duplicate',
922-
draft: draft,
923-
deepKey: props.deepKey,
924-
index: index,
925-
});
926-
};
895+
const pasteAfter = useCallback(
896+
(index: number) => {
897+
dispatch({
898+
type: 'pasteAfter',
899+
draft: draft,
900+
deepKey: props.deepKey,
901+
index: index,
902+
virtualClipboard: virtualClipboard,
903+
});
904+
},
905+
[props.deepKey]
906+
);
927907

928-
const removeAt = (index: number) => {
929-
dispatch({
930-
type: 'removeAt',
931-
draft: draft,
932-
deepKey: props.deepKey,
933-
index: index,
934-
});
935-
};
908+
const insertBefore = useCallback(
909+
(index: number) => {
910+
dispatch({
911+
type: 'insertBefore',
912+
draft: draft,
913+
deepKey: props.deepKey,
914+
index: index,
915+
});
916+
},
917+
[props.deepKey]
918+
);
936919

937-
/** Focus the field header (the clickable "summary" part). */
938-
const focusFieldHeader = (deepKey: string, index: number) => {
939-
document.getElementById(`summary-for-${deepKey}.${order[index]}`)?.focus();
940-
};
920+
const insertAfter = useCallback(
921+
(index: number) => {
922+
dispatch({
923+
type: 'insertAfter',
924+
draft: draft,
925+
deepKey: props.deepKey,
926+
index: index,
927+
});
928+
},
929+
[props.deepKey]
930+
);
941931

942-
const moveUp = (index: number) => {
943-
if (index > 0) {
932+
const duplicate = useCallback(
933+
(index: number) => {
944934
dispatch({
945-
type: 'moveUp',
935+
type: 'duplicate',
946936
draft: draft,
947937
deepKey: props.deepKey,
948938
index: index,
949939
});
950-
}
951-
};
940+
},
941+
[props.deepKey]
942+
);
952943

953-
const moveDown = (index: number) => {
954-
if (index < order.length - 1) {
944+
const removeAt = useCallback(
945+
(index: number) => {
955946
dispatch({
956-
type: 'moveDown',
947+
type: 'removeAt',
957948
draft: draft,
958949
deepKey: props.deepKey,
959950
index: index,
960951
});
961-
}
952+
},
953+
[props.deepKey]
954+
);
955+
956+
/** Focus the field header (the clickable "summary" part). */
957+
const focusFieldHeader = (deepKey: string, index: number) => {
958+
document.getElementById(`summary-for-${deepKey}.${order[index]}`)?.focus();
962959
};
963960

961+
const moveUp = useCallback(
962+
(index: number) => {
963+
if (index > 0) {
964+
dispatch({
965+
type: 'moveUp',
966+
draft: draft,
967+
deepKey: props.deepKey,
968+
index: index,
969+
});
970+
}
971+
},
972+
[props.deepKey]
973+
);
974+
975+
const moveDown = useCallback(
976+
(index: number) => {
977+
if (index < order.length - 1) {
978+
dispatch({
979+
type: 'moveDown',
980+
draft: draft,
981+
deepKey: props.deepKey,
982+
index: index,
983+
});
984+
}
985+
},
986+
[props.deepKey]
987+
);
988+
964989
/** Copies the item data to the state so it can be "pasted" via the context menu later. */
965990
const copyToVirtualClipboard = (index: number) => {
966991
const key = order[index];
@@ -1014,24 +1039,27 @@ DocEditor.ArrayField = (props: FieldProps) => {
10141039
}
10151040

10161041
/** Handler for using the arrow keys when the array item's header is focused. */
1017-
function handleKeyDown(e: KeyboardEvent, arrayKey: string) {
1018-
if (!e.target) {
1019-
return;
1020-
}
1021-
// Move the items up and down using the up/down arrow keys.
1022-
// Collapse and expand the item using the left/right arrow keys.
1023-
if (e.key === 'ArrowUp') {
1024-
e.preventDefault();
1025-
moveUp(order.indexOf(arrayKey));
1026-
} else if (e.key === 'ArrowDown') {
1027-
e.preventDefault();
1028-
moveDown(order.indexOf(arrayKey));
1029-
} else if (e.key === 'ArrowLeft') {
1030-
(e.target as HTMLElement).closest('details')!.open = false;
1031-
} else if (e.key === 'ArrowRight') {
1032-
(e.target as HTMLElement).closest('details')!.open = true;
1033-
}
1034-
}
1042+
const handleKeyDown = useCallback(
1043+
(e: KeyboardEvent, arrayKey: string) => {
1044+
if (!e.target) {
1045+
return;
1046+
}
1047+
// Move the items up and down using the up/down arrow keys.
1048+
// Collapse and expand the item using the left/right arrow keys.
1049+
if (e.key === 'ArrowUp') {
1050+
e.preventDefault();
1051+
moveUp(order.indexOf(arrayKey));
1052+
} else if (e.key === 'ArrowDown') {
1053+
e.preventDefault();
1054+
moveDown(order.indexOf(arrayKey));
1055+
} else if (e.key === 'ArrowLeft') {
1056+
(e.target as HTMLElement).closest('details')!.open = false;
1057+
} else if (e.key === 'ArrowRight') {
1058+
(e.target as HTMLElement).closest('details')!.open = true;
1059+
}
1060+
},
1061+
[props.deepKey]
1062+
);
10351063

10361064
const addButtonRow = (
10371065
<div className="DocEditor__ArrayField__add">
@@ -1313,7 +1341,7 @@ DocEditor.OneOfField = (props: FieldProps) => {
13131341
}
13141342
);
13151343
return unsubscribe;
1316-
}, [props.draft, props.deepKey]);
1344+
}, [props.deepKey]);
13171345

13181346
// When the dropdown receives focus, highlight the <input> text so that it can
13191347
// be easily searched.

packages/root-cms/ui/components/DocEditor/fields/BooleanField.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Checkbox} from '@mantine/core';
2-
import {useEffect, useState} from 'preact/hooks';
2+
import {useCallback, useEffect, useState} from 'preact/hooks';
33
import * as schema from '../../../../core/schema.js';
44
import {FieldProps} from './FieldProps.js';
55

@@ -8,10 +8,13 @@ export function BooleanField(props: FieldProps) {
88
const label = field.checkboxLabel || 'Enabled';
99
const [value, setValue] = useState<boolean>(false);
1010

11-
function onChange(newValue: boolean) {
12-
setValue(newValue);
13-
props.draft.updateKey(props.deepKey, newValue);
14-
}
11+
const onChange = useCallback(
12+
(newValue: boolean) => {
13+
setValue(newValue);
14+
props.draft.updateKey(props.deepKey, newValue);
15+
},
16+
[props.deepKey]
17+
);
1518

1619
useEffect(() => {
1720
const unsubscribe = props.draft.subscribe(

0 commit comments

Comments
 (0)