---
title: "React state set inside a `mousemove` / `pointermove` / `scroll` handler (lint/setstate-per-pointer-move)"
description: "lint/setstate-per-pointer-move: React Bits Magnet and SpotlightCard call `setPosition` on every pointer event (and Magnet attaches one window listener per…"
canonical: https://void-design.vercel.app/rules/lint/setstate-per-pointer-move
lastModified: 2026-09-16
---

# React state set inside a `mousemove` / `pointermove` / `scroll` handler

`lint/setstate-per-pointer-move` · severity **warn** · category Lint · detected by `void lint` and `void audit`

## Why it matters

React Bits Magnet and SpotlightCard call `setPosition` on every pointer event (and Magnet attaches one window listener per instance), so the component and its children re-render up to 120×/s and INP degrades with the number of instances on the page.

## How to fix it

Write per-frame values to a ref'd element's style or a CSS custom property (`el.style.setProperty('--x', …)`) or a MotionValue `.set()`; attach listeners to the element, gate with `(hover: hover) and (pointer: fine)` and skip under reduced motion.

## Example

```tsx
const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
  const r = e.currentTarget.getBoundingClientRect();
  e.currentTarget.style.setProperty('--x', `${e.clientX - r.left}px`);
  e.currentTarget.style.setProperty('--y', `${e.clientY - r.top}px`);
};
```

## More lint rules

`void lint` reports 67 rules in this category. Static source checks with no browser: Tailwind v4 silent failures, Next.js 16 API traps, React render-body bugs, accessibility markup, SEO files and motion hygiene.

- `animate-undefined` `animate-*` class with no matching `--animate-*` theme variable or `@keyframes` — [lint/animate-undefined](https://void-design.vercel.app/rules/lint/animate-undefined)
- `random-in-render` `Math.random()` / `Date.now()` / `new Date()` evaluated in a component render body — [lint/random-in-render](https://void-design.vercel.app/rules/lint/random-in-render)
- `duplicate-children-not-hidden` Children repeated for a marquee/loop without `aria-hidden` or `inert` on the copies — [lint/duplicate-children-not-hidden](https://void-design.vercel.app/rules/lint/duplicate-children-not-hidden)
- `conditional-hook` Hook called inside a JSX expression, `&&`, ternary or callback — [lint/conditional-hook](https://void-design.vercel.app/rules/lint/conditional-hook)
- `svg-global-id` Hardcoded `id` on an SVG `<filter>`, gradient, `<clipPath>` or `<mask>` inside a reusable component — [lint/svg-global-id](https://void-design.vercel.app/rules/lint/svg-global-id)
- `allocation-per-frame` `new Intl.NumberFormat` / `Intl.NumberFormat(` / `new Color(` inside an animation-frame callback — [lint/allocation-per-frame](https://void-design.vercel.app/rules/lint/allocation-per-frame)
- `hardcoded-device-pixel-ratio` Canvas/WebGL renders with a literal `devicePixelRatio: 2` (or `width * 2`) — [lint/hardcoded-device-pixel-ratio](https://void-design.vercel.app/rules/lint/hardcoded-device-pixel-ratio)
- `button-missing-type` `<button>` (or `as="button"` default) without an explicit `type` — [lint/button-missing-type](https://void-design.vercel.app/rules/lint/button-missing-type)

Detected by `void lint` and `void audit`. Explain it in a terminal: `void rules lint/setstate-per-pointer-move`
