Skip to content

[BUG] AnimateNumber buggy layout calculations when using Suspense #3258

@pondorasti

Description

@pondorasti

I'm trying to incorporate AnimateNumber in a codebase that relies on Suspense boundaries and keep running into layout bugs around initial mount animations.

1

Sometimes the magnitude number is not properly reflected. You can see in my video how it initially renders as 495K even though the real value is 495, and then any subsequent client re-renders correctly show the correct value.

CleanShot.2025-06-12.at.17.50.20.mp4

2

Changing the suffix incorrectly calculates the width on first mount

CleanShot.2025-06-12.at.17.54.31.mp4

Pseudo Source

I didn't get a chance to put together a repo, for the time being I can drop my source in here. If you give this to claude/cursor, it can probably do a pretty good job at mocking a sandbox with nextjs.

interface ModelHeaderSearchFiltersTotalCountProps {
  debouncedSearchQuery: string
  searchOrder: SearchEmojiOrder
}

function ModelHeaderSearchFiltersTotalCount({
  debouncedSearchQuery,
  searchOrder,
}: ModelHeaderSearchFiltersTotalCountProps) {
  const { modelId } = useModelParams()
  const { data: emojisSearchData } = useSuspenseEmojisSearch({
    variables: { order: searchOrder, query: debouncedSearchQuery, modelIds: [modelId] },
  })
  const totalCount = useMemo(() => emojisSearchData.pages[0]?.searchEmojis?.totalCount ?? 0, [emojisSearchData])

  return (
    <div className="mb-0.5 flex flex-col flex-nowrap items-end gap-[3px]">
      <span className="text-[10px] leading-[12px] font-medium tracking-wider text-gray-500 uppercase">Showing</span>
      <span className="text-[15px] leading-none font-medium text-gray-950 dark:text-gray-50">
        <AnimateNumber
          initial={false}
          suffix={totalCount === 1 ? " image" : " images"}
          className="tabular-nums"
          format={{ notation: "compact", compactDisplay: "short" }}
        >
          {totalCount}
        </AnimateNumber>
      </span>
    </div>
  )
}

function ModelHeaderSearchFiltersTotalCountFallback() {
  return (
    <div className="mb-0.5 flex flex-col flex-nowrap items-end justify-center gap-[4px]">
      <div className="h-3 w-13 rounded-sm bg-gray-100 dark:bg-gray-900/70" />
      <div className="h-3 w-18 rounded-sm bg-gray-100 dark:bg-gray-900/70" />
    </div>
  )
}


function Page() {
  return (
      <Suspense fallback={<ModelHeaderSearchFiltersTotalCountFallback />}>
        <ModelHeaderSearchFiltersTotalCount debouncedSearchQuery={debouncedSearchQuery} searchOrder={searchOrder} />
      </Suspense>
  )
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions