{"record":{"id":"3eccf3fe4518e639","repo":"halo-dev/halo","slug":"please-select-a-snapshot","errorCode":null,"errorMessage":"Please select a snapshot","messagePattern":"Please select a snapshot","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"ui/console-src/components/snapshots/SnapshotContent.vue","lineNumber":28,"sourceCode":"const props = withDefaults(\n  defineProps<{\n    cacheKey: string;\n    name: string;\n    snapshotNames?: string[];\n    getApi: (snapshotName: string) => Promise<ContentWrapper>;\n  }>(),\n  {\n    snapshotNames: () => [],\n  }\n);\n\nconst { name, snapshotNames, cacheKey } = toRefs(props);\n\nconst { data: snapshot, isLoading } = useQuery({\n  queryKey: SNAPSHOT_QUERY_KEY(cacheKey, name, snapshotNames),\n  queryFn: async () => {\n    if (!snapshotNames.value?.length) {\n      throw new Error(\"Please select a snapshot\");\n    }\n\n    return await props.getApi(snapshotNames.value[0]);\n  },\n  onError(err) {\n    if (err instanceof Error) {\n      Toast.error(err.message);\n    }\n  },\n  enabled: computed(() => !!name.value && !!snapshotNames.value?.length),\n});\n\nconst sanitizedContent = computed(() => {\n  return DOMPurify.sanitize(snapshot.value?.content || \"\");\n});\n</script>\n<template>\n  <OverlayScrollbarsComponent","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/ui/console-src/components/snapshots/SnapshotContent.vue#L10-L46","documentation":"Thrown inside a TanStack Query queryFn in SnapshotContent.vue when the `snapshotNames` prop array is empty. It is a defensive guard before calling props.getApi(snapshotNames.value[0]) — accessing index 0 of an empty array would pass undefined to the API. The component's `enabled` computed already requires a non-empty snapshotNames, so under normal flow this throw is unreachable; it exists to fail fast if the query is invoked without a selection.","triggerScenarios":"The useQuery runs (name is truthy) but snapshotNames is empty/undefined at queryFn execution time. Because enabled = !!name && !!snapshotNames?.length, this only fires if the enabled predicate is bypassed (manual refetch, initialData with empty array, or a transient state where name updates before snapshotNames). Selecting zero snapshots in the parent UI and forcing a refetch would also trip it.","commonSituations":"Parent component binds snapshotNames reactively and there is a window where name resolves but snapshotNames has not loaded; a developer calls queryClient.refetchQueries on this key during a state where no snapshot is picked; the parent passes snapshotNames=() => [] default and never sets it; race between two refs after a snapshot deletion.","solutions":["Ensure the parent always passes a non-empty snapshotNames when it renders SnapshotContent with a valid name — gate the component render on snapshotNames.length.","If this surfaces in a toast (onError shows err.message), treat it as a state-sync bug: verify the order of reactive updates for name vs snapshotNames.","Avoid manual refetchQueries on SNAPSHOT_QUERY_KEY when no snapshot is selected.","Consider returning early (return null/undefined) instead of throwing so an empty selection is a no-op rather than an error."],"exampleFix":"// before\nqueryFn: async () => {\n  if (!snapshotNames.value?.length) {\n    throw new Error(\"Please select a snapshot\");\n  }\n  return await props.getApi(snapshotNames.value[0]);\n},\n// after — disable query instead of throwing, since enabled already guards it\nenabled: computed(() => !!name.value && !!snapshotNames.value?.length),\nqueryFn: async () => {\n  const first = snapshotNames.value?.[0];\n  if (!first) return null;\n  return await props.getApi(first);\n},","handlingStrategy":"validation","validationCode":"// Guard before invoking the API inside queryFn\nfunction getFirstSnapshotName(names: string[] | undefined): string | null {\n  return Array.isArray(names) && names.length > 0 ? names[0] : null;\n}\n// usage:\nconst first = getFirstSnapshotName(snapshotNames.value);\nif (!first) return null;\nreturn await props.getApi(first);","typeGuard":"function hasSelectedSnapshot(names: string[] | undefined): names is string[] {\n  return Array.isArray(names) && names.length > 0;\n}","tryCatchPattern":"// queryFn onError is already wired; keep the throw, but only toast for genuine API errors\nonError(err) {\n  if (err instanceof Error && err.message !== \"Please select a snapshot\") {\n    Toast.error(err.message);\n  }\n}","preventionTips":["Gate SnapshotContent rendering in the parent on snapshotNames.length > 0.","Align the useQuery `enabled` predicate with the queryFn's precondition so the query never runs in a throwing state.","Prefer returning null over throwing for 'empty selection' states — reserve throws for true API failures."],"tags":["vue","tanstack-query","snapshots","validation","defensive-guard"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}