Skip to content

How to Fix ESLint 'Modifying Value Passed to Hook' Warning in React

Problem

When I run ESLint on my React component, I got this warning:

ESLint warning
ESLint: Modifying a value previously passed as an argument to a hook is not allowed.
Consider moving the modification before calling the hook. (react-hooks/rules-of-hooks)

Environment

  • React 18.x
  • ESLint with eslint-plugin-react-hooks
  • Node.js 20.x

What happened?

I was building a video player component. I needed to track the playing state in a ref so I could access it inside a callback without causing re-renders.

Here’s my code:

VideoPlayer.jsx
import { useRef, useEffect, useState } from 'react'
function VideoPlayer({ src }) {
const [isPlaying, setIsPlaying] = useState(false)
// I pass isPlaying to useRef as initial value
const isPlayingRef = useRef(isPlaying)
// Then I sync the ref when state changes
useEffect(() => {
isPlayingRef.current = isPlaying
}, [isPlaying])
return <video src={src} />
}

The code works fine at runtime. But ESLint keeps complaining about the line where I modify isPlayingRef.current.

How to solve it?

I tried a few things.

First attempt: I thought about disabling the ESLint rule:

VideoPlayer.jsx
// eslint-disable-next-line react-hooks/rules-of-hooks
const isPlayingRef = useRef(isPlaying)

But this felt wrong. I’m hiding the problem instead of fixing it.

Second attempt: I read the ESLint error more carefully. It says “consider moving the modification before calling the hook.” But that doesn’t make sense for useRef - the whole point is to create a mutable container.

Then I realized the issue: ESLint sees that I’m passing isPlaying (a state value) to useRef, and then modifying isPlayingRef.current later. ESLint thinks this is problematic because hook arguments should be stable.

The fix: Initialize with null instead of the state value:

VideoPlayer.jsx
import { useRef, useEffect, useState } from 'react'
function VideoPlayer({ src }) {
const [isPlaying, setIsPlaying] = useState(false)
// Initialize with null, not state
const isPlayingRef = useRef(null)
// Sync the ref in useEffect
useEffect(() => {
isPlayingRef.current = isPlaying
}, [isPlaying])
return <video src={src} />
}

Now ESLint is happy. And the behavior is identical - the useEffect runs on the first render and syncs isPlayingRef.current to isPlaying anyway.

The reason

Why does ESLint complain about the original code?

The ESLint plugin for React hooks enforces immutability rules. When you write:

Problem code
const isPlayingRef = useRef(isPlaying)

You pass isPlaying as an argument to useRef. Later, when you modify isPlayingRef.current, ESLint sees this as “modifying a value previously passed as an argument to a hook.”

This warning exists because:

  • Hook arguments should be stable and predictable
  • Passing mutable state to hooks can lead to confusing behavior
  • ESLint cannot distinguish between safe and unsafe mutations

The key insight: this is an ESLint warning, not a React error. Your original code is valid React and runs without issues. But ESLint flags it to encourage patterns that are more explicit.

Why useRef(null) is better:

  • More explicit - clearly shows the ref will be assigned later
  • Standard pattern - matches how refs are used for DOM elements
  • Avoids confusion - separates initialization from state values

Common Mistakes

Mistake 1: Forgetting to sync the ref

Wrong - ref never gets updated
const isPlayingRef = useRef(null)
// Missing: useEffect to sync isPlayingRef.current = isPlaying

The ref will stay null forever.

Mistake 2: Using ref.current in dependency arrays

Wrong - ref.current in dependency
useEffect(() => {
// some effect
}, [isPlayingRef.current]) // This won't trigger re-renders

Refs in dependency arrays do not trigger re-renders. Use the state value instead:

Correct - use state in dependency
useEffect(() => {
// some effect
}, [isPlaying]) // Use state, not ref.current

Mistake 3: Defining refs after using them

If you use a ref in a useCallback dependency array, define the ref first:

Correct order
// Define ref first
const isPlayingRef = useRef(null)
// Then use it in useCallback
const callback = useCallback(() => {
console.log(isPlayingRef.current)
}, [isPlayingRef])

When to Use useRef vs useState

A quick comparison:

useRefuseState
No re-render on changeTriggers re-render
Mutable .current propertyImmutable (use setter)
For DOM referencesFor UI state
For previous valuesFor current values
For non-reactive dataFor reactive data

Summary

In this post, I showed how to fix the ESLint warning “Modifying a value passed to hook” when using useRef with state values. The key point is to initialize useRef(null) instead of useRef(stateValue), then sync the ref inside a useEffect. This satisfies ESLint’s immutability rules while keeping identical runtime behavior.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments