Skip to content

Why Does ESLint Warn When You Initialize useRef with State?

Problem

When I tried to create a ref that tracks a state value, I got this ESLint error:

ESLint error
ESLint: Modifying a value previously passed as an argument to a hook is not allowed.
Consider moving the modification before calling the hook.

Here’s the code that triggered it:

MediaPlayer.jsx
import { useState, useRef } from 'react';
function MediaPlayer() {
const [isPlaying, setIsPlaying] = useState(false);
// ESLint error here!
const isPlayingRef = useRef(isPlaying);
const handlePlay = () => {
setIsPlaying(true);
};
return <button onClick={handlePlay}>Play</button>;
}

I was confused. I thought refs were meant to be mutable. Why does ESLint complain?

Environment

  • React 18.x
  • ESLint with react-hooks plugin

What happened?

I wanted to keep a ref in sync with my isPlaying state. My reasoning was simple: pass the state value to useRef(), and the ref gets initialized with that value.

But ESLint flagged this line:

Problem code
const isPlayingRef = useRef(isPlaying);

The warning says I’m “modifying a value previously passed as an argument to a hook.” But I’m not modifying anything! I’m just reading isPlaying.

Here’s what I didn’t understand at the time.

The reason

The issue is about how React hooks work, not about refs being mutable.

When you call useRef(initialValue), React only uses that initialValue on the first render. On subsequent renders, React ignores the argument entirely and returns the existing ref object.

But ESLint’s rule doesn’t know this. It sees:

  1. isPlaying is a state variable (can change)
  2. You’re passing it to a hook as an argument
  3. The hook’s argument should be “stable”

ESLint treats hook arguments as something that shouldn’t change between renders. State values, by definition, can change. So passing state to useRef() triggers the warning.

Here’s the confusing part: even though refs are designed to be mutable (you can freely assign to ref.current), the initialization argument is treated as immutable by ESLint.

How to solve it?

I tried a few approaches.

First attempt: Disable the ESLint rule

MediaPlayer.jsx
// DON'T DO THIS
const isPlayingRef = useRef(isPlaying); // eslint-disable-line

This “works” but it’s wrong. The warning exists for a reason. I was just hiding the problem.

Second attempt: Initialize with null

I read the React docs more carefully. The recommended pattern is to initialize refs with null or a static value:

MediaPlayer.jsx
import { useState, useRef, useEffect } from 'react';
function MediaPlayer() {
const [isPlaying, setIsPlaying] = useState(false);
// Initialize with null - ESLint is happy
const isPlayingRef = useRef(null);
// Sync ref with state explicitly
useEffect(() => {
isPlayingRef.current = isPlaying;
}, [isPlaying]);
const handlePlay = () => {
setIsPlaying(true);
// ref.current will be updated via effect
};
return <button onClick={handlePlay}>Play</button>;
}

This works. The ESLint warning is gone.

The pattern is:

  1. Initialize the ref with null (a stable value)
  2. Sync the ref with state in a useEffect
  3. The effect runs on mount and whenever isPlaying changes

The behavior is identical to what I originally wanted, but now the code follows React conventions.

For expensive initializations

Sometimes you need to initialize a ref with a computed value, not null. React docs show this pattern:

VideoPlayer.jsx
import { useRef } from 'react';
function VideoPlayer() {
const playerRef = useRef(null);
// Conditional assignment in render phase
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
return <div>Video Player</div>;
}

This is safe because:

  • The useRef(null) call has a stable argument
  • The conditional logic happens after the hook call
  • It only runs once (on first render when current is null)

A common mistake

I initially thought that useRef(isPlaying) would keep the ref updated when state changes. It doesn’t.

WrongExample.jsx
// This does NOT keep the ref in sync!
const isPlayingRef = useRef(isPlaying);
// isPlayingRef.current stays at the initial value forever
// even when isPlaying state changes

The initial value is only used once, on the first render. If you want the ref to track state changes, you need the useEffect pattern I showed above.

Summary

In this post, I explained why ESLint warns when you initialize useRef with a state value. The key point is that hook arguments should be stable, even though refs themselves are mutable.

The fix is simple: initialize with useRef(null) instead of useRef(stateValue), and sync the ref in a useEffect if needed.

This pattern:

  • Avoids ESLint warnings
  • Follows React conventions
  • Makes the data flow explicit
  • Works exactly the same as the “wrong” version

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