{"record":{"id":"6fe804f0d7e2e00d","repo":"CherryHQ/cherry-studio","slug":"useimagepreviewtransform-requires-minzoom-maxzo","errorCode":null,"errorMessage":"useImagePreviewTransform requires minZoom <= maxZoom","messagePattern":"useImagePreviewTransform requires minZoom <= maxZoom","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/src/components/composites/image-preview/use-image-preview-transform.ts","lineNumber":74,"sourceCode":"  zoom: clamp(toFiniteNumber(transform?.zoom, DEFAULT_TRANSFORM.zoom), minZoom, maxZoom)\n})\n\nconst transformsEqual = (left: ImagePreviewTransform, right: ImagePreviewTransform) =>\n  left.flipX === right.flipX &&\n  left.flipY === right.flipY &&\n  left.offsetX === right.offsetX &&\n  left.offsetY === right.offsetY &&\n  left.rotation === right.rotation &&\n  left.zoom === right.zoom\n\nexport function useImagePreviewTransform({\n  initialTransform,\n  maxZoom = 5,\n  minZoom = 1,\n  zoomStep = 0.25\n}: ImagePreviewTransformOptions = {}): ImagePreviewTransformControls {\n  if (minZoom > maxZoom) {\n    throw new Error('useImagePreviewTransform requires minZoom <= maxZoom')\n  }\n\n  if (zoomStep <= 0 || !Number.isFinite(zoomStep)) {\n    throw new Error('useImagePreviewTransform requires zoomStep > 0')\n  }\n\n  const initialValue = React.useMemo(\n    () => normalizeTransform(initialTransform, minZoom, maxZoom),\n    [initialTransform, maxZoom, minZoom]\n  )\n  const [transform, setTransform] = React.useState<ImagePreviewTransform>(initialValue)\n\n  const update = React.useCallback(\n    (nextUpdate: ImagePreviewTransformUpdate) => {\n      setTransform((current) => {\n        const patch = typeof nextUpdate === 'function' ? nextUpdate(current) : nextUpdate\n        const next = normalizeTransform({ ...current, ...patch }, minZoom, maxZoom)\n        return transformsEqual(current, next) ? current : next","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/packages/ui/src/components/composites/image-preview/use-image-preview-transform.ts#L56-L92","documentation":"Thrown synchronously by the useImagePreviewTransform React hook during render when the caller passes a minZoom greater than maxZoom. The hook clamps zoom into [minZoom, maxZoom], so an inverted range is meaningless; it fails fast with a clear invariant message rather than producing broken zoom behavior. Defaults are minZoom=1, maxZoom=5, so this only fires when both options are explicitly supplied in the wrong order.","triggerScenarios":"Calling `useImagePreviewTransform({ minZoom: 2, maxZoom: 1 })`; passing a maxZoom smaller than a non-default minZoom (e.g. minZoom: 3, maxZoom: 2); computing zoom bounds from config that can invert.","commonSituations":"Swapping the two numeric props; deriving min/max from user settings or responsive breakpoints where max can drop below min; bad defaults in a wrapper component.","solutions":["Ensure the passed options satisfy minZoom <= maxZoom (or omit both to use the safe defaults minZoom=1, maxZoom=5).","If bounds come from dynamic config, clamp them at the call site before passing: `const [lo, hi] = order(minRaw, maxRaw)`.","If only one bound is configurable, keep the other at a default that cannot invert (e.g. fixed maxZoom=5 with a minZoom in [1,5])."],"exampleFix":"// before\nuseImagePreviewTransform({ minZoom: 2, maxZoom: 1 })\n// after\nuseImagePreviewTransform({ minZoom: 1, maxZoom: 5 })","handlingStrategy":"validation","validationCode":"// Validate options before calling the hook.\nfunction safeZoomOptions(opts: ImagePreviewTransformOptions): ImagePreviewTransformOptions {\n  const minZoom = opts.minZoom ?? 1\n  const maxZoom = opts.maxZoom ?? 5\n  if (minZoom > maxZoom) {\n    throw new Error(`Invalid zoom range: minZoom ${minZoom} > maxZoom ${maxZoom}`)\n  }\n  return opts\n}\nconst controls = useImagePreviewTransform(safeZoomOptions(props))","typeGuard":"const isValidZoomOptions = (o: ImagePreviewTransformOptions): boolean => {\n  const min = o.minZoom ?? 1\n  const max = o.maxZoom ?? 5\n  return min <= max && Number.isFinite(min) && Number.isFinite(max)\n}","tryCatchPattern":"// Hooks throw during render, so wrap the consuming component in an ErrorBoundary rather than try/catch at the call site.\n<ErrorBoundary fallback={<ImagePreviewFallback />}>\n  <ImagePreview minZoom={props.min} maxZoom={props.max} />\n</ErrorBoundary>","preventionTips":["Omit minZoom/maxZoom to accept the safe defaults (1 and 5).","When bounds come from config, normalize them at the call site so min is always <= max before passing.","Add a unit test asserting useImagePreviewTransform throws for inverted ranges and accepts valid ones."],"tags":["react","hook","validation","image-preview"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}