taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 9e6bd7fe9657ce371c030c48e68c11a31ed210d3
parent 1c2925aac9ebb7787da6055083b7b35511b2a690
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Fri, 30 Oct 2026 18:37:50 -0300

FIX: multiple re-renders

using useEffect to solved multiple changes in useState is bad pattern, all state should be calculated in one run by the function and without side effect.
the dependency on "initial" is solved by a key attributed and innercomponent.

Diffstat:
Mpackages/taler-merchant-webui/src/screens/AdminAccountFormScreen.tsx | 58+++++++++++++++++++++++++++++++++-------------------------
1 file changed, 33 insertions(+), 25 deletions(-)

diff --git a/packages/taler-merchant-webui/src/screens/AdminAccountFormScreen.tsx b/packages/taler-merchant-webui/src/screens/AdminAccountFormScreen.tsx @@ -8,7 +8,7 @@ */ import type { VNode } from "preact"; -import { useEffect, useRef, useState } from "preact/hooks"; +import { useState } from "preact/hooks"; import { isValidMerchantInstanceId, normalizeMerchantInstanceId, @@ -71,17 +71,33 @@ function durationUs( return value?.d_us ?? fallback; } -export function AdminAccountFormScreen({ +export function AdminAccountFormScreen( + props: AdminAccountFormScreenProps, +): VNode { + const initial = props.initial ?? EMPTY; + return ( + <AdminAccountFormInner + {...props} + key={`${props.mode}:${initial.id}`} + initial={initial} + /> + ); +} + +function AdminAccountFormInner({ mode, - initial = EMPTY, + initial, phoneRegex, onSubmit, onBack, -}: AdminAccountFormScreenProps): VNode { +}: AdminAccountFormScreenProps & { + initial: AdminAccountFormValue; +}): VNode { const { t } = useTranslation(); const [value, setValue] = useState<AdminAccountFormValue>(initial); const [confirmPassword, setConfirmPassword] = useState(""); - const [showAdvanced, setShowAdvanced] = useState(mode === "edit"); + const [showAdvancedRequested, setShowAdvanced] = useState(mode === "edit"); + const [ignoreInvalidLogo, setIgnoreInvalidLogo] = useState(false); const [customTiming, setCustomTiming] = useState( mode === "edit" || Boolean( @@ -107,28 +123,12 @@ export function AdminAccountFormScreen({ const [validation, setValidation] = useState(""); const [pending, setPending] = useState(false); const logoStatus = useImageDataUrlStatus(value.logo || ""); - const hydratedIdentity = useRef<string | undefined>(undefined); + const showAdvanced = + showAdvancedRequested || (logoStatus === "invalid" && !ignoreInvalidLogo); const isPhoneInvalid = Boolean(value.phone_number) && !isValidMfaPhone(value.phone_number!, phoneRegex); - useEffect(() => { - const identity = `${mode}:${initial.id}`; - if (hydratedIdentity.current === identity) return; - hydratedIdentity.current = identity; - setValue(initial); - setPayDelayUs(durationUs(initial.default_pay_delay, 86_400_000_000)); - setRefundDelayUs(durationUs(initial.default_refund_delay, 0)); - setWireDelayUs( - durationUs(initial.default_wire_transfer_delay, 86_400_000_000), - ); - setRounding(initial.default_wire_transfer_rounding_interval || "NONE"); - }, [initial, mode]); - - useEffect(() => { - if (logoStatus === "invalid") setShowAdvanced(true); - }, [logoStatus]); - const change = <K extends keyof AdminAccountFormValue>( key: K, next: AdminAccountFormValue[K], @@ -339,7 +339,11 @@ export function AdminAccountFormScreen({ <button type="button" - onClick={() => setShowAdvanced(!showAdvanced)} + onClick={() => { + const next = !showAdvanced; + setIgnoreInvalidLogo(!next && logoStatus === "invalid"); + setShowAdvanced(next); + }} class="flex w-full items-center justify-between rounded-xl border border-gray-200 bg-white p-4 text-left text-sm font-bold text-gray-900 hover:bg-gray-50" > <span>{t`Advanced business configuration`}</span> @@ -368,7 +372,11 @@ export function AdminAccountFormScreen({ label={t`Logo`} helpText={t`Shown on payment pages and receipts.`} value={value.logo || ""} - onChange={(next) => change("logo", next)} + onChange={(next) => { + setIgnoreInvalidLogo(false); + setShowAdvanced(true); + change("logo", next); + }} /> </div> <LocationInput