From 5da385da2b7855b8bcb68a58eef0ed21864d9842 Mon Sep 17 00:00:00 2001 From: johnny1093 <46250921+jsmorabito@users.noreply.github.com> Date: Sat, 11 Jul 2026 10:17:22 -0400 Subject: [PATCH] fix: use a real Preact hook to force re-renders, not this.forceUpdate() The previous fix (61f15f0) restored this.forceUpdate() based on tracing through preact's diff/component internals, which showed function components are invoked as c.render(...) - a method call that should bind `this` to a real Component instance with a working forceUpdate(). That trace was confirmed against the library source, but you tested it and the settings list still didn't update in place after adding a command, so something in that chain isn't panning out in practice (possibly the setTimeout-based render scheduling, possibly something else) - not worth continuing to debug blind. Replacing it with the standard, unambiguous approach: a useState counter bumped to force a re-render, the documented Preact/React idiom that doesn't depend on any internal `this` plumbing. Applied to CommandViewer, MacroViewer, and MobileModifyComponent everywhere they previously called this.forceUpdate(). Please rebuild and re-test: add/remove/reorder/rename/recolor a command from the settings list without switching tabs, same for macros, and the mobile icon-picker modal. --- src/ui/components/MacroViewer.tsx | 16 +++++++---- src/ui/components/commandViewerComponent.tsx | 30 ++++++++++++-------- src/ui/components/mobileModifyComponent.tsx | 23 +++++++-------- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/ui/components/MacroViewer.tsx b/src/ui/components/MacroViewer.tsx index b6443ca..6f486e6 100644 --- a/src/ui/components/MacroViewer.tsx +++ b/src/ui/components/MacroViewer.tsx @@ -1,5 +1,6 @@ import { Platform } from "obsidian"; import { Fragment, h } from "preact"; +import { useState } from "preact/hooks"; import t from "src/l10n"; import CommanderPlugin from "src/main"; import { Macro } from "src/types"; @@ -12,10 +13,13 @@ interface MacroBuilderProps { plugin: CommanderPlugin; macros: Macro[]; } -export default function MacroViewer( - this: { forceUpdate: () => void }, - { plugin, macros }: MacroBuilderProps -): h.JSX.Element { +export default function MacroViewer({ + plugin, + macros, +}: MacroBuilderProps): h.JSX.Element { + const [, setUpdateTick] = useState(0); + const forceUpdate = (): void => setUpdateTick((tick) => tick + 1); + const handleBuilder = (macro: Macro, idx?: number): void => { const onClose = (updatedMacro: Macro): void => { macros.splice( @@ -25,7 +29,7 @@ export default function MacroViewer( ); void plugin.saveSettings(); - this.forceUpdate(); + forceUpdate(); updateMacroCommands(plugin); modal.close(); }; @@ -36,7 +40,7 @@ export default function MacroViewer( const handleDelete = (idx: number): void => { macros.splice(idx, 1); void plugin.saveSettings(); - this.forceUpdate(); + forceUpdate(); updateMacroCommands(plugin); }; diff --git a/src/ui/components/commandViewerComponent.tsx b/src/ui/components/commandViewerComponent.tsx index f734191..3287f41 100644 --- a/src/ui/components/commandViewerComponent.tsx +++ b/src/ui/components/commandViewerComponent.tsx @@ -1,4 +1,5 @@ import { createContext, Fragment, h } from "preact"; +import { useState } from "preact/hooks"; import CommanderPlugin from "src/main"; import CommandComponent from "./commandComponent"; import CommandManagerBase from "src/manager/commands/commandManager"; @@ -18,10 +19,15 @@ interface CommandViewerProps { children?: h.JSX.Element | h.JSX.Element[]; sortable?: boolean; } -export default function CommandViewer( - this: { forceUpdate: () => void }, - { manager, plugin, children, sortable = true }: CommandViewerProps -): h.JSX.Element { +export default function CommandViewer({ + manager, + plugin, + children, + sortable = true, +}: CommandViewerProps): h.JSX.Element { + const [, setUpdateTick] = useState(0); + const forceUpdate = (): void => setUpdateTick((tick) => tick + 1); + return ( @@ -45,7 +51,7 @@ export default function CommandViewer( ).didChooseRemove()) ) { await manager.removeCommand(cmd); - this.forceUpdate(); + forceUpdate(); } }} handleUp={(): void => { @@ -55,7 +61,7 @@ export default function CommandViewer( idx - 1 ); void manager.reorder(); - this.forceUpdate(); + forceUpdate(); }} handleDown={(): void => { arrayMoveMutable( @@ -64,7 +70,7 @@ export default function CommandViewer( idx + 1 ); void manager.reorder(); - this.forceUpdate(); + forceUpdate(); }} handleRename={async ( name @@ -72,7 +78,7 @@ export default function CommandViewer( cmd.name = name; await plugin.saveSettings(); await manager.reorder(); - this.forceUpdate(); + forceUpdate(); }} handleNewIcon={async (): Promise => { const newIcon = @@ -83,7 +89,7 @@ export default function CommandViewer( cmd.icon = newIcon; await plugin.saveSettings(); await manager.reorder(); - this.forceUpdate(); + forceUpdate(); } dispatchEvent( new Event("cmdr-icon-changed") @@ -108,7 +114,7 @@ export default function CommandViewer( mode || modes[currentIdx + 1]; await plugin.saveSettings(); await manager.reorder(); - this.forceUpdate(); + forceUpdate(); }} handleColorChange={async ( color?: string @@ -116,7 +122,7 @@ export default function CommandViewer( cmd.color = color; await plugin.saveSettings(); await manager.reorder(); - this.forceUpdate(); + forceUpdate(); }} /> ); @@ -146,7 +152,7 @@ export default function CommandViewer( const pair = await chooseNewCommand(plugin); await manager.addCommand(pair); await manager.reorder(); - this.forceUpdate(); + forceUpdate(); }} > {t("Add command")} diff --git a/src/ui/components/mobileModifyComponent.tsx b/src/ui/components/mobileModifyComponent.tsx index 34644e6..eddfb5f 100644 --- a/src/ui/components/mobileModifyComponent.tsx +++ b/src/ui/components/mobileModifyComponent.tsx @@ -1,24 +1,23 @@ import { h } from "preact"; -import { useEffect } from "preact/hooks"; +import { useEffect, useState } 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( - this: { forceUpdate: () => void }, - { - plugin, - modal: controller, - }: { - plugin: CommanderPlugin; - modal: MobileModifyModal; - } -): h.JSX.Element { +export default function MobileModifyComponent({ + plugin, + modal: controller, +}: { + plugin: CommanderPlugin; + modal: MobileModifyModal; +}): h.JSX.Element { + const [, setUpdateTick] = useState(0); + useEffect(() => { const update = (): void => { - this.forceUpdate(); + setUpdateTick((tick) => tick + 1); }; addEventListener("cmdr-icon-changed", update); return (): void => removeEventListener("cmdr-icon-changed", update);