CherryHQ/cherry-studio · error · Error

useImagePreviewTransform requires zoomStep > 0

Error message

useImagePreviewTransform requires zoomStep > 0

What it means

Thrown by the useImagePreviewTransform hook (in @cherrystudio/ui) during render when the zoomStep option is zero, negative, NaN, or Infinity. zoomStep is the delta added/subtracted on each zoomIn/zoomOut call, so a non-positive or non-finite value would make zoom controls non-functional or produce NaN zoom levels. The guard runs before any state is created, failing fast on invalid configuration.

Source

Thrown at packages/ui/src/components/composites/image-preview/use-image-preview-transform.ts:78

  left.flipX === right.flipX &&
  left.flipY === right.flipY &&
  left.offsetX === right.offsetX &&
  left.offsetY === right.offsetY &&
  left.rotation === right.rotation &&
  left.zoom === right.zoom

export function useImagePreviewTransform({
  initialTransform,
  maxZoom = 5,
  minZoom = 1,
  zoomStep = 0.25
}: ImagePreviewTransformOptions = {}): ImagePreviewTransformControls {
  if (minZoom > maxZoom) {
    throw new Error('useImagePreviewTransform requires minZoom <= maxZoom')
  }

  if (zoomStep <= 0 || !Number.isFinite(zoomStep)) {
    throw new Error('useImagePreviewTransform requires zoomStep > 0')
  }

  const initialValue = React.useMemo(
    () => normalizeTransform(initialTransform, minZoom, maxZoom),
    [initialTransform, maxZoom, minZoom]
  )
  const [transform, setTransform] = React.useState<ImagePreviewTransform>(initialValue)

  const update = React.useCallback(
    (nextUpdate: ImagePreviewTransformUpdate) => {
      setTransform((current) => {
        const patch = typeof nextUpdate === 'function' ? nextUpdate(current) : nextUpdate
        const next = normalizeTransform({ ...current, ...patch }, minZoom, maxZoom)
        return transformsEqual(current, next) ? current : next
      })
    },
    [maxZoom, minZoom]
  )

View on GitHub (pinned to 726446b54c)

Solutions

  1. Pass a positive finite zoomStep (the default 0.25 is sane): useImagePreviewTransform({ zoomStep: 0.25 }) or omit the option entirely.
  2. If zoomStep is dynamic, sanitize it before the call: const safeZoomStep = zoomStep > 0 && Number.isFinite(zoomStep) ? zoomStep : 0.25.
  3. Check the call site feeding zoomStep — if it comes from user settings/preferences, ensure the schema clamps the minimum above 0.
  4. Add a unit test asserting the hook accepts its documented range and rejects 0/negative/NaN.

Example fix

// before
useImagePreviewTransform({ zoomStep: 0 }) // throws

// after
useImagePreviewTransform({ zoomStep: 0.25 })
Defensive patterns

Strategy: validation

Validate before calling

// Validate before calling the hook (hook-level guards cannot be conditional)
const safeZoomStep =
  typeof zoomStep === 'number' && zoomStep > 0 && Number.isFinite(zoomStep)
    ? zoomStep
    : 0.25 // sane default
useImagePreviewTransform({ zoomStep: safeZoomStep, minZoom, maxZoom })

Type guard

const isValidZoomStep = (v: unknown): v is number =>
  typeof v === 'number' && v > 0 && Number.isFinite(v)

Prevention

When it happens

Trigger: Calling useImagePreviewTransform({ zoomStep: 0 }), with a negative value (zoomStep: -0.5), or passing a computed value that evaluated to NaN/Infinity (e.g. zoomStep: someRatio where someRatio is NaN). Also triggered if zoomStep is derived from user input or a config file that was mis-parsed into a string then cast with Number().

Common situations: Passing zoomStep: 0 thinking it disables zoom; deriving zoomStep from a division that can yield NaN (e.g. 1/count when count is 0); reading a numeric setting from preferences that defaults to 0 before the user configures it; serializing/deserializing config where the number became a string and Number() coerced an empty string to 0.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/5a56ca0f020584c7. Report an issue: GitHub.