'Mouse Out of Element and Mouse Into Another Element Does Not Reset State
Code: https://codesandbox.io/s/objective-darwin-w0i5pk?file=/src/App.js
Description: This is just 4 gray squares that each get their own shade of gray. I want to change the background color of each square when the user hovers over each, but I want the hover color to be +10 in RGB of what it was originally.
Issue: When I mouse/hover out of one of the gray squares and mouse/hover into another gray square, the first square does not switch back to its initial color state.
Help: Can someone explain why it is doing this and how to fix it because I have no idea?
Note: I am trying not to use CSS for the hover because I am specifying the backgroundColor with JS.
import React, { useState } from "react";
import "./styles.css";
const tabs = [
{ name: "1", img: [] },
{ name: "2", img: [] },
{ name: "3", img: [] },
{ name: "4", img: [] }
];
const initialState = {};
tabs.forEach((t, i) => {
initialState[i] = false;
});
export default function App() {
const [hover, setHover] = useState(initialState);
return (
<div className="App">
{tabs.map((t, i) => {
const v = 50 - (i + 1) * 10;
const val = hover[i] ? v + 10 : v;
return (
<div
key={t.name}
className="tab"
onMouseOver={() => {
setHover({
...hover,
[i]: true
});
}}
onMouseLeave={() => {
setHover({
...hover,
[i]: false
});
}}
onMouseOut={() => {
setHover({
...hover,
[i]: false
});
}}
style={{
backgroundColor: `rgb(${val}, ${val}, ${val})`,
height: "100px",
width: "100px"
}}
>
<p>{t.name}</p>
</div>
);
})}
</div>
);
}
.App {
font-family: sans-serif;
text-align: center;
margin: 0;
padding: 0;
}
* {
margin: 0;
padding: 0;
}
This picture only shows the initial state:
Solution 1:[1]
setState calls are not what a human would consider "immediate". Instead, the calls to the state setter as queued inside React internal mechanisms. Consider this:
const [state, setState] = useState(0)
// somewhere
setState(state + 1)
setState(state + 1)
In this case, you do not end up with 2 but 1, because while you call setState twice to increment by one, you really are calling it as:
setState(1)
setState(1)
This is the exact issue in your code with the callbacks, you have
// enter
setState({ ...state, [i]: true })
// leave
setState({ ...state, [i]: false })
so when both get called, you apply the "leave" with the wrong previous state.
This is why setState has another pattern, setState(prevState => nextState)
setState(prevState => prevState + 1)
setState(prevState => prevState + 1)
Like this, you do end up with the value 2 because the second call is then using the "correct" previous state.
In your case, you need:
// enter
setState(prevState => ({ ...prevState, [i]: true }))
// leave
setState(prevState => ({ ...prevState, [i]: false }))
Solution 2:[2]
This is happening because you also keep the previous values in your state. You should update in this way
onMouseOver={() => {
setHover({
[i]: true
});
}}
onMouseLeave={() => {
setHover({
[i]: false
});
}}
onMouseOut={() => {
setHover({
[i]: false
});
}}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jakub Kotrs |
| Solution 2 | Evren |

