fix: restore this.forceUpdate() - it was never actually broken

Corrects commit b83e10a. I removed these calls on the assumption
that `this` is undefined in a Preact function component, based on
standard JS semantics. That's wrong for Preact specifically: its
diff implementation invokes function components as `c.render(props,
state, context)` (see node_modules/preact/src/diff/index.js), which
is a method call and therefore binds `this` to Preact's internal
per-instance "PFC backing instance" - a real object with a working
forceUpdate(). So this.forceUpdate() was live code all along, and
removing it broke in-place re-rendering: adding/removing/renaming/
recoloring a command or macro would save correctly but the settings
list wouldn't visually update until you switched tabs and back
(reported by the user after testing).

Restores the calls, now with an explicit `this: { forceUpdate: () =>
void }` parameter on each component function so TypeScript actually
knows the type instead of silently treating it as `any` like the
original code did.
This commit is contained in:
johnny1093 2026-07-11 10:11:20 -04:00
parent b3b2f7088a
commit 61f15f04e0
3 changed files with 37 additions and 17 deletions

View file

@ -12,10 +12,10 @@ interface MacroBuilderProps {
plugin: CommanderPlugin;
macros: Macro[];
}
export default function MacroViewer({
plugin,
macros,
}: MacroBuilderProps): h.JSX.Element {
export default function MacroViewer(
this: { forceUpdate: () => void },
{ plugin, macros }: MacroBuilderProps
): h.JSX.Element {
const handleBuilder = (macro: Macro, idx?: number): void => {
const onClose = (updatedMacro: Macro): void => {
macros.splice(
@ -25,6 +25,7 @@ export default function MacroViewer({
);
void plugin.saveSettings();
this.forceUpdate();
updateMacroCommands(plugin);
modal.close();
};
@ -35,6 +36,7 @@ export default function MacroViewer({
const handleDelete = (idx: number): void => {
macros.splice(idx, 1);
void plugin.saveSettings();
this.forceUpdate();
updateMacroCommands(plugin);
};

View file

@ -18,12 +18,10 @@ interface CommandViewerProps {
children?: h.JSX.Element | h.JSX.Element[];
sortable?: boolean;
}
export default function CommandViewer({
manager,
plugin,
children,
sortable = true,
}: CommandViewerProps): h.JSX.Element {
export default function CommandViewer(
this: { forceUpdate: () => void },
{ manager, plugin, children, sortable = true }: CommandViewerProps
): h.JSX.Element {
return (
<Fragment>
<ManagerContext.Provider value={manager}>
@ -47,6 +45,7 @@ export default function CommandViewer({
).didChooseRemove())
) {
await manager.removeCommand(cmd);
this.forceUpdate();
}
}}
handleUp={(): void => {
@ -56,6 +55,7 @@ export default function CommandViewer({
idx - 1
);
void manager.reorder();
this.forceUpdate();
}}
handleDown={(): void => {
arrayMoveMutable(
@ -64,6 +64,7 @@ export default function CommandViewer({
idx + 1
);
void manager.reorder();
this.forceUpdate();
}}
handleRename={async (
name
@ -71,6 +72,7 @@ export default function CommandViewer({
cmd.name = name;
await plugin.saveSettings();
await manager.reorder();
this.forceUpdate();
}}
handleNewIcon={async (): Promise<void> => {
const newIcon =
@ -81,6 +83,7 @@ export default function CommandViewer({
cmd.icon = newIcon;
await plugin.saveSettings();
await manager.reorder();
this.forceUpdate();
}
dispatchEvent(
new Event("cmdr-icon-changed")
@ -105,6 +108,7 @@ export default function CommandViewer({
mode || modes[currentIdx + 1];
await plugin.saveSettings();
await manager.reorder();
this.forceUpdate();
}}
handleColorChange={async (
color?: string
@ -112,6 +116,7 @@ export default function CommandViewer({
cmd.color = color;
await plugin.saveSettings();
await manager.reorder();
this.forceUpdate();
}}
/>
);
@ -141,6 +146,7 @@ export default function CommandViewer({
const pair = await chooseNewCommand(plugin);
await manager.addCommand(pair);
await manager.reorder();
this.forceUpdate();
}}
>
{t("Add command")}

View file

@ -1,17 +1,29 @@
import { h } from "preact";
import { useEffect } from "preact/hooks";
import t from "src/l10n";
import { ObsidianIcon } from "src/util";
import MobileModifyModal from "../mobileModifyModal";
import { ColorPicker } from "./ColorPicker";
import CommanderPlugin from "src/main";
export default function MobileModifyComponent({
plugin,
modal: controller,
}: {
plugin: CommanderPlugin;
modal: MobileModifyModal;
}): h.JSX.Element {
export default function MobileModifyComponent(
this: { forceUpdate: () => void },
{
plugin,
modal: controller,
}: {
plugin: CommanderPlugin;
modal: MobileModifyModal;
}
): h.JSX.Element {
useEffect(() => {
const update = (): void => {
this.forceUpdate();
};
addEventListener("cmdr-icon-changed", update);
return (): void => removeEventListener("cmdr-icon-changed", update);
}, []);
return (
<div className="cmdr-mobile-modify-grid">
<div