Skip to content

[bug]: react-hooks/purity reports Cannot call impure function during render with Math.random() from Sidebar #8540

@karlhorky

Description

@karlhorky

Describe the bug

The react-hooks/purity ESLint rule based on the React Compiler reports Cannot call impure function during render with the Math.random() call in the SidebarMenuSkeleton component:

function SidebarMenuSkeleton({
  className,
  showIcon = false,
  ...props
}: React.ComponentProps<"div"> & {
  showIcon?: boolean
}) {
  // Random width between 50 to 90%.
  const width = React.useMemo(() => {
    return `${Math.floor(Math.random() * 40) + 50}%` // 💥 react-hooks/purity error, see details below
  }, [])

Affected component/components

Sidebar

How to reproduce

  1. Create a Next.js app
    $ mkdir repro-shadcnui-react-hooks-purity-error
    $ cd repro-shadcnui-react-hooks-purity-error
    $ pnpm create [email protected] . --app --no-turbopack --no-src-dir --no-eslint --import-alias @/\* --tailwind --typescript 
    Creating a new Next.js app in /Users/k/p/repro-shadcnui-react-hooks-purity-error.
    
    ...
    
    dependencies:
    + next 15.5.5
    + react 19.1.0
    + react-dom 19.1.0
    
    devDependencies:
    + @tailwindcss/postcss 4.1.14
    + @types/node 20.19.21
    + @types/react 19.2.2
    + @types/react-dom 19.2.2
    + tailwindcss 4.1.14
    + typescript 5.9.3
  2. Use shadcn/ui to add a sidebar component
    $ pnpm dlx shadcn@latest add sidebar
    ...
    ✔ Created 8 files:
      ...
      - components/ui/sidebar.tsx
  3. Install ESLint dependencies
    $ pnpm add --save-dev eslint @eslint/js typescript typescript-eslint eslint-config-flat-gitignore eslint-plugin-react-hooks
    Packages: +157 -2
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
    Progress: resolved 295, reused 246, downloaded 2, added 37, done
    
    devDependencies:
    + @eslint/js 9.37.0 (9.38.0 is available)
    + eslint 9.37.0 (9.38.0 is available)
    + eslint-config-flat-gitignore 2.1.0
    + eslint-plugin-react-hooks 7.0.0
    + typescript 5.9.3
    + typescript-eslint 8.46.1 (8.46.2 is available)
    
    Done in 3s using pnpm v10.19.0
  4. Configure the react-hooks/purity rule in eslint.config.mjs, based partly on the typescript-eslint Getting Started docs
    import eslint from '@eslint/js';
    import gitignore from 'eslint-config-flat-gitignore';
    import reactHooks from 'eslint-plugin-react-hooks';
    import tseslint from 'typescript-eslint';
    
    export default tseslint.config(
      gitignore(),
      eslint.configs.recommended,
      tseslint.configs.recommended,
      {
        ignores: ['.next/**'],
        plugins: {
          'react-hooks': reactHooks,
        },
        rules: {
          'react-hooks/purity': 'error',
        },
      },
    );
  5. Lint the Sidebar file and receive an error 💥
    $ pnpm eslint components/ui/sidebar.tsx --max-warnings 0
    
    /Users/k/p/repro-shadcnui-react-hooks-purity-error/components/ui/sidebar.tsx
      611:26  error  Error: Cannot call impure function during render
    
    `Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
    
    /Users/k/p/repro-shadcnui-react-hooks-purity-error/components/ui/sidebar.tsx:611:26
      609 |   // Random width between 50 to 90%.
      610 |   const width = React.useMemo(() => {
    > 611 |     return `${Math.floor(Math.random() * 40) + 50}%`
          |                          ^^^^^^^^^^^^^ Cannot call impure function
      612 |   }, [])
      613 |
      614 |   return (  react-hooks/purity
    
    ✖ 1 problem (1 error, 0 warnings)

Codesandbox/StackBlitz link

Suggested solution

Switch useMemo to useState as per the example in the react-hooks/purity docs, disabling the react-naming-convention/use-state rule if necessary:

-  const width = React.useMemo(() => {
+  // eslint-disable-next-line react-naming-convention/use-state -- Allow value-only state destructuring for resolving react-hooks/purity problem https://github.com/shadcn-ui/ui/issues/8540
+  const [width] = React.useState(() => {
     return `${Math.floor(Math.random() * 40) + 50}%`
-  }, [])
+  })

Logs

System Info

eslint-plugin-react-hooks 7.0.0

Before submitting

  • I've made research efforts and searched the documentation
  • I've searched for existing issues

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