health score: move the non-food early return past the hook

`if (!isEdible(category)) return null` sat on the line above `useQuery`, which
made the hook conditional. React counts hooks per component instance, so one
drawer reused for two products -- a soap and then a biscuit, which is ordinary
browsing in the global catalogue -- went 0 hooks then 1 and threw "rendered more
hooks than during the previous render".

That does not degrade the panel, it unmounts the tree: the health score then
disappears for EVERY product until the page is reloaded, which reads exactly
like the feature having been switched off.

The guard now sits after the hook, and `enabled` carries the intent the early
return was protecting -- a non-food product still asks the service nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 12:23:50 +05:30
parent ced0236ca7
commit dd718d4e81

View File

@@ -56,14 +56,25 @@ export function HealthScorePanel({
populated for both, so nothing in the response separates them. Our own populated for both, so nothing in the response separates them. Our own
category does, so the decision is made here until they publish theirs. category does, so the decision is made here until they publish theirs.
Before the network call, deliberately: there is nothing to ask about. Still no network call for one — see `enabled` below — but the EARLY RETURN
that used to sit here has moved past the hook.
It read `if (!isEdible(category)) return null` on the line above `useQuery`,
which made the hook conditional. React counts hooks per component instance, so
one drawer reused for two products — a soap and then a biscuit, which is
ordinary browsing in the global catalogue — went 0 hooks then 1, and React
threw "rendered more hooks than during the previous render". That does not
degrade the panel, it unmounts the tree: the health score then disappears for
EVERY product until the page is reloaded, which reads exactly like the feature
being switched off.
*/ */
if (!isEdible(category)) return null; const isFood = isEdible(category);
const query = useQuery({ const query = useQuery({
queryKey: ['nutrition', brand, imageId], queryKey: ['nutrition', brand, imageId],
queryFn: () => nutritionApi.forProduct(brand, imageId), queryFn: () => nutritionApi.forProduct(brand, imageId),
enabled: Boolean(brand && imageId), // Non-food asks nothing, which is what the early return was protecting.
enabled: Boolean(brand && imageId && isFood),
// Nutrition for a packaged product does not change during a trading day. // Nutrition for a packaged product does not change during a trading day.
staleTime: 60 * 60_000, staleTime: 60 * 60_000,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
@@ -72,6 +83,9 @@ export function HealthScorePanel({
retry: false, retry: false,
}); });
// Past the hook, so the count is the same on every render.
if (!isFood) return null;
/* /*
* No catalogue link, so there is nothing to look up. * No catalogue link, so there is nothing to look up.
* *