I've made a simple example of a counter and want to console log value on mouse click, but it always displays 0. How can fix it?
import "./App.css";
import { useState } from "react";
import { useEffect } from "react";
function App() {
const [num, setNum] = useState(0);
const handler = () => {
console.log(num);
};
useEffect(() => {
window.addEventListener("click", handler);
}, []);
return (
<div className="App">
<header className="App-header">
<div
onClick={() => {
setNum(num 1);
}}
>
Add
</div>
<h1>{num}</h1>
</header>
</div>
);
}
export default App;
CodePudding user response:
You should be using the useEffect
to check for changes in the num
state rather than adding an unnecessary addEventListener
.
const { useEffect, useState } = React;
function Example() {
const [ num, setNum ] = useState(0);
function handler() {
setNum(num 1);
}
useEffect(() => console.log(num), [num]);
return (
<div className="App">
<header className="App-header">
<div onClick={handler}>Add</div>
<h1>{num}</h1>
</header>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
CodePudding user response:
The problem is that whenever your component re-renders itself (due to state change), a new value is set for num
, and a new function is created for handler
.
However, your event handler inside the useEffect
will never see the new function created for handler
, therefore it will keep calling the old function, which still holds a reference to the old value of num
, which is zero.
Here is a quick fix to get your code to kinda start working:
import "./App.css";
import { useEffect, useState, useCallback } from "react";
function App() {
const [num, setNum] = useState(0);
const handler = useCallback(() => {
console.log(num);
}, [num]);
useEffect(() => {
window.addEventListener("click", handler);
return () => {
window.removeEventListener("click", handler);
};
}, [handler]);
return (
<div className="App">
<header className="App-header">
<div
onClick={() => {
setNum(num 1);
}}
>
Add
</div>
<h1>{num}</h1>
</header>
</div>
);
}
export default App;
When you run this, you will see that the value printed in the console is lagging behind the value printed in the console. I'll leave it to you to figure out why.
If you really want to use an event listener, rather than doing what the other answers have suggested, you can try something like this:
import "./App.css";
import { useEffect, useState, useRef, useCallback } from "react";
function App() {
const [num, setNum] = useState(0);
const numRef = useRef(num);
const handler = useCallback(() => {
console.log(numRef.current);
}, []);
useEffect(() => {
window.addEventListener("click", handler);
return () => {
window.removeEventListener("click", handler);
};
}, [handler]);
return (
<div className="App">
<header className="App-header">
<div
onClick={() => {
setNum(num => {
numRef.current = num 1;
return numRef.current;
})
}}
>
Add
</div>
<h1>{num}</h1>
</header>
</div>
);
}
export default App;
This last one works for two reasons:
- The
handler
is using a reference to the latest version ofnum
, and this reference will always stay up-to-date because we update it in thesetNum
callback. This also means thathandler
will not change between renders. - The event handler will not have to be removed each time
num
changes, which was causing the console.log to lag behind the value of num.