From 2a7ae0702dfc56e69bf845ded19888c29519414a Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Mon, 11 May 2026 19:37:52 +0200 Subject: [PATCH] fix(events): CustomerAccountPicker hooks order crashed /admin/events/new --- .../admin/CustomerAccountPicker.tsx | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/admin/CustomerAccountPicker.tsx b/frontend/src/components/admin/CustomerAccountPicker.tsx index 11028a04..9dea5bce 100644 --- a/frontend/src/components/admin/CustomerAccountPicker.tsx +++ b/frontend/src/components/admin/CustomerAccountPicker.tsx @@ -33,14 +33,17 @@ const labelFor = (c: { email: string; displayName?: string | null; companyName?: export const CustomerAccountPicker: React.FC = ({ value, onChange, disabled }) => { const { t } = useTranslation(); + // Rules of Hooks: the feature-flag gate (early-return) is moved to + // the very end of this hook list (see end of function). The previous + // shape did `if (!customerPortalEnabled) return null` BEFORE the + // useState/useRef/useEffect calls below, which caused the hook count + // to differ between renders the moment the React Query for + // /admin/feature-flags resolved (first render: enabled=false from + // DEFAULT_FLAGS → return null; second render: enabled=true → hooks + // run → "Rendered more hooks than during the previous render" + // crash). That tanked the entire /admin/events/new page through + // the global error boundary. PR #458 reviewer flag. const customerPortalEnabled = useFeatureEnabled('customerPortal'); - - // Gate the entire picker on the customerPortal feature flag. When off, - // the backend returns 410 on /admin/customers/search anyway, but hiding - // the UI here keeps the event form clean and removes the dangling - // "Customer accounts" label that would otherwise appear above an - // empty/error placeholder. - if (!customerPortalEnabled) return null; const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [isOpen, setIsOpen] = useState(false); @@ -107,6 +110,14 @@ export const CustomerAccountPicker: React.FC = ({ value, onChange, disabl [t] ); + // Feature-flag gate (deliberately placed AFTER all hooks — see the + // long comment at the top of this component for why). When the + // customerPortal flag is off the backend returns 410 on + // /admin/customers/search anyway, but hiding the UI here keeps the + // event form clean and removes the dangling "Customer accounts" + // label that would otherwise appear above an empty placeholder. + if (!customerPortalEnabled) return null; + return (