diff --git a/apps/client/src/widgets/react/Button.tsx b/apps/client/src/widgets/react/Button.tsx index f5317f843..585e8b1dd 100644 --- a/apps/client/src/widgets/react/Button.tsx +++ b/apps/client/src/widgets/react/Button.tsx @@ -1,6 +1,6 @@ import type { RefObject } from "preact"; import type { CSSProperties } from "preact/compat"; -import { useRef, useMemo } from "preact/hooks"; +import { useMemo } from "preact/hooks"; import { memo } from "preact/compat"; import { CommandNames } from "../../components/app_context"; @@ -22,7 +22,7 @@ export interface ButtonProps { title?: string; } -const Button = memo(({ name, buttonRef: _buttonRef, className, text, onClick, keyboardShortcut, icon, primary, disabled, size, style, triggerCommand, ...restProps }: ButtonProps) => { +const Button = memo(({ name, buttonRef, className, text, onClick, keyboardShortcut, icon, primary, disabled, size, style, triggerCommand, ...restProps }: ButtonProps) => { // Memoize classes array to prevent recreation const classes = useMemo(() => { const classList: string[] = ["btn"]; @@ -42,8 +42,6 @@ const Button = memo(({ name, buttonRef: _buttonRef, className, text, onClick, ke return classList.join(" "); }, [primary, className, size]); - const buttonRef = _buttonRef ?? useRef(null); - // Memoize keyboard shortcut rendering const shortcutElements = useMemo(() => { if (!keyboardShortcut) return null; diff --git a/apps/client/src/widgets/react/FormCheckbox.tsx b/apps/client/src/widgets/react/FormCheckbox.tsx index 9eafcec9e..873c6a8fc 100644 --- a/apps/client/src/widgets/react/FormCheckbox.tsx +++ b/apps/client/src/widgets/react/FormCheckbox.tsx @@ -6,8 +6,7 @@ import { CSSProperties, memo } from "preact/compat"; import { useUniqueName } from "./hooks"; interface FormCheckboxProps { - id?: string; - name?: string; + name: string; label: string | ComponentChildren; /** * If set, the checkbox label will be underlined and dotted, indicating a hint. When hovered, it will show the hint text. @@ -19,9 +18,9 @@ interface FormCheckboxProps { containerStyle?: CSSProperties; } -const FormCheckbox = memo(({ name, id: _id, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) => { - const id = _id ?? useUniqueName(name); +const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) => { const labelRef = useRef(null); + const id = useUniqueName(name); // Fix: Move useEffect outside conditional useEffect(() => { diff --git a/apps/client/src/widgets/react/Modal.tsx b/apps/client/src/widgets/react/Modal.tsx index 3971ebabd..e97914a45 100644 --- a/apps/client/src/widgets/react/Modal.tsx +++ b/apps/client/src/widgets/react/Modal.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useRef, useMemo, useCallback } from "preact/hooks"; +import { useContext, useEffect, useRef, useMemo } from "preact/hooks"; import { t } from "../../services/i18n"; import { ComponentChildren } from "preact"; import type { CSSProperties, RefObject } from "preact/compat"; @@ -6,6 +6,7 @@ import { openDialog } from "../../services/dialog"; import { ParentComponent } from "./react_utils"; import { Modal as BootstrapModal } from "bootstrap"; import { memo } from "preact/compat"; +import { useSyncedRef } from "./hooks"; interface ModalProps { className: string; @@ -64,10 +65,9 @@ interface ModalProps { stackable?: boolean; } -export default function Modal({ children, className, size, title, header, footer, footerStyle, footerAlignment, onShown, onSubmit, helpPageId, minWidth, maxWidth, zIndex, scrollable, onHidden: onHidden, modalRef: _modalRef, formRef: _formRef, bodyStyle, show, stackable }: ModalProps) { - const modalRef = _modalRef ?? useRef(null); +export default function Modal({ children, className, size, title, header, footer, footerStyle, footerAlignment, onShown, onSubmit, helpPageId, minWidth, maxWidth, zIndex, scrollable, onHidden: onHidden, modalRef: externalModalRef, formRef, bodyStyle, show, stackable }: ModalProps) { + const modalRef = useSyncedRef(externalModalRef); const modalInstanceRef = useRef(); - const formRef = _formRef ?? useRef(null); const parentWidget = useContext(ParentComponent); const elementToFocus = useRef(); @@ -145,10 +145,10 @@ export default function Modal({ children, className, size, title, header, footer {onSubmit ? ( -
{ + { e.preventDefault(); onSubmit(); - }, [onSubmit])}> + }}> {children}
) : ( diff --git a/apps/client/src/widgets/react/NoteAutocomplete.tsx b/apps/client/src/widgets/react/NoteAutocomplete.tsx index dd97f90d4..223dbb5d4 100644 --- a/apps/client/src/widgets/react/NoteAutocomplete.tsx +++ b/apps/client/src/widgets/react/NoteAutocomplete.tsx @@ -1,9 +1,9 @@ -import { useRef } from "preact/hooks"; import { t } from "../../services/i18n"; import { useEffect } from "preact/hooks"; import note_autocomplete, { Options, type Suggestion } from "../../services/note_autocomplete"; import type { RefObject } from "preact"; import type { CSSProperties } from "preact/compat"; +import { useSyncedRef } from "./hooks"; interface NoteAutocompleteProps { id?: string; @@ -19,8 +19,8 @@ interface NoteAutocompleteProps { noteId?: string; } -export default function NoteAutocomplete({ id, inputRef: _ref, text, placeholder, onChange, onTextChange, container, containerStyle, opts, noteId, noteIdChanged }: NoteAutocompleteProps) { - const ref = _ref ?? useRef(null); +export default function NoteAutocomplete({ id, inputRef: externalInputRef, text, placeholder, onChange, onTextChange, container, containerStyle, opts, noteId, noteIdChanged }: NoteAutocompleteProps) { + const ref = useSyncedRef(externalInputRef); useEffect(() => { if (!ref.current) return; diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 2234a58af..eeb13f7fc 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -509,4 +509,16 @@ export function useLegacyImperativeHandlers(handlers: Record) useEffect(() => { Object.assign(parentComponent as never, handlers); }, [ handlers ]); +} + +export function useSyncedRef(externalRef?: RefObject, initialValue: T | null = null): RefObject { + const ref = useRef(initialValue); + + useEffect(() => { + if (externalRef) { + externalRef.current = ref.current; + } + }, [ ref, externalRef ]); + + return ref; } \ No newline at end of file diff --git a/apps/client/src/widgets/type_widgets/options/ai_settings.tsx b/apps/client/src/widgets/type_widgets/options/ai_settings.tsx index c60c85cac..3b94a9d13 100644 --- a/apps/client/src/widgets/type_widgets/options/ai_settings.tsx +++ b/apps/client/src/widgets/type_widgets/options/ai_settings.tsx @@ -132,7 +132,7 @@ interface SingleProviderSettingsProps { } function SingleProviderSettings({ provider, title, apiKeyDescription, baseUrlDescription, modelDescription, validationErrorMessage, apiKeyOption, baseUrlOption, modelOption }: SingleProviderSettingsProps) { - const [ apiKey, setApiKey ] = apiKeyOption ? useTriliumOption(apiKeyOption) : []; + const [ apiKey, setApiKey ] = useTriliumOption(apiKeyOption ?? baseUrlOption); const [ baseUrl, setBaseUrl ] = useTriliumOption(baseUrlOption); const isValid = (apiKeyOption ? !!apiKey : !!baseUrl);