Subscribe to what you render

Subscribe to the boolean you render; read handler-only values on call.

From vercel-react-best-practices: rerender-derived-state and rerender-defer-reads.

Render per pixel or crossing

Drag the edge or slider, or press Sweep.

useWindowWidth()Bad
Renders
0
Output changed
0
Wasted
0
const isMobile = useWindowWidth() < 768
useMediaQuery()Good
Renders
0
Output changed
0
Wasted
0
useMediaQuery('(max-width: 767px)')

function useMediaQuery(query) {
  return useSyncExternalStore(
    (onChange) => {
      const mql = matchMedia(query)
      mql.addEventListener('change', onChange)
      return () => mql.removeEventListener('change', onChange)
    },
    () => matchMedia(query).matches,
    () => false,
  )
}

function Sidebar() {
  const isMobile = useMediaQuery('(max-width: 767px)')
  return <nav className={isMobile ? 'mobile' : 'desktop'} />
}

Read the URL on click

Change the params, then press Share in both panes.

page 1
location.search
useSearchParams()Bad
Not shared yet
Renders
0
Shares
0
Wasted
0
searchParams.get('ref')
Read in the handlerGood
Not shared yet
Renders
0
Shares
0
Wasted
0
new URLSearchParams(location.search)
function ShareButton({ chatId }) {
  const handleShare = () => {
    const ref = new URLSearchParams(window.location.search).get('ref')
    shareChat(chatId, { ref })
  }
  return <button onClick={handleShare}>Share</button>
}

Depend on the boolean

Sweep across 768px and compare effect runs.

[width]Bad
Effect runs
0
enableMobileMode()
0
    useEffect(() => { if (width < 768) enableMobileMode() }, [width])
    [isMobile]Good
    Effect runs
    0
    enableMobileMode()
    0
      useEffect(() => { if (isMobile) enableMobileMode() }, [isMobile])

      const isMobile = width < 768
      useEffect(() => {
        if (isMobile) enableMobileMode()
      }, [isMobile])

      Why it matters, pros and cons

      Why it matters

      Render on isMobile, not width. Values only a handler uses: read them on click.

      Pros

      • One render per crossing, not per pixel
      • Handler reads are never stale
      • Effects fire once per transition

      Cons

      • Rendering the number itself still needs a subscription
      • Layout-only changes belong in CSS @media, not JS
      • Media queries have no server answer; hydration corrects it