[bug]: `react-hooks/purity` reports `Cannot call impure function during render` with `Math.random()` from Sidebar
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
- 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 - Use shadcn/ui to add a sidebar component
$ pnpm dlx shadcn@latest add sidebar ... ✔ Created 8 files: ... - components/ui/sidebar.tsx - 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 - Configure the
react-hooks/purityrule ineslint.config.mjs, based partly on thetypescript-eslintGetting Started docsimport 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', }, }, ); - 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
- https://codesandbox.io/p/devbox/sweet-wildflower-mtmqdw?file=%2Fcomponents%2Fui%2Fsidebar.tsx%3A611%2C53&workspaceId=ws_GfAuHrswXyA1DoeSwsjjjz
- GitHub reproduction: https://github.com/karlhorky/repro-shadcnui-react-hooks-purity-error
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
- [x] I've made research efforts and searched the documentation
- [x] I've searched for existing issues
Hello! @karlhorky fixed it, let me know!
I also encountered this problem
Same problem here.
Forcing update of shadcdn sidebar component
% npx shadcn@latest add sidebar --overwrite
✔ Checking registry.
✔ Updating CSS variables in src/app/globals.css
✔ Installing dependencies.
ℹ Updated 3 files:
- src/components/ui/tooltip.tsx
- src/components/ui/input.tsx
- src/components/ui/sidebar.tsx
ℹ Skipped 5 files: (files might be identical, use --overwrite to overwrite)
- src/components/ui/button.tsx
- src/components/ui/separator.tsx
- src/components/ui/sheet.tsx
- src/hooks/use-mobile.ts
- src/components/ui/skeleton.tsx
-
But
/ ... /src/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).
/ ... /src/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
We cannot upgrade to next 16 and to latest eslint recommended configuration due to this error (which of course happens in other components, I suppose)
Actual eslint.config.mjs
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
export default [
{
ignores: [
"node_modules/**",
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
"lambda/dist",
],
},
...nextCoreWebVitals,
];
Is there a workaround? adn/or is there a planned fix?
This solution from [#8546](https://github.com/shadcn-ui/ui/pull/8546/commits/c26d8ab2f289034f44334737615b4640193f4789) resolved the react-hooks/purity linting error in my project:
// Random width between 50 to 90%.
const [width] = React.useState(() => `${Math.floor(Math.random() * 40) + 50}%`)
sidebar.tsx line 610