Currently I have a map function that render a serial of image, and I realized that they share the same hover state, which means they will perform the same action when hovered. Is there are any standard practice to map duplicate components while assigning them unique/individual properies?
{itemData.map((item) => (
<ImageListItem key={item.img}>
<img
src={item.img}
alt={item.title}
loading="lazy"
onMouseOver={() => {setHover(true)}}
onMouseOut={() => {setHover(false)}}
style={{ transform: hover ? 'scale(1.5, 1.5)' : null }}
/>
<ImageListItemBar
title={item.title}
subtitle={item.author}
actionIcon={
<IconButton
sx={{ color: 'rgba(255, 255, 255, 0.54)' }}
aria-label={`info about ${item.title}`}
>
<InfoIcon />
</IconButton>
}
/>
To provide unique properties, you need to have something that uniquely identifies your image component and use it to manage your state. In your case, your state hover should be an array or an object, not a boolean. Since you are using item.img as a key, I assume it is unique and hence it can help in your state management like this:
const [hover, setHover] = useState({});
{itemData.map((item) => (
<ImageListItem key={item.img}>
<img
src={item.img}
alt={item.title}
loading="lazy"
onMouseOver={() => setHover({...hover, [item.img]: true})}
onMouseOut={() => setHover({...hover, [item.img]: false})}
style={{ transform: hover ? 'scale(1.5, 1.5)' : null }}
/>
<ImageListItemBar
title={item.title}
subtitle={item.author}
actionIcon={
<IconButton
sx={{ color: 'rgba(255, 255, 255, 0.54)' }}
aria-label={`info about ${item.title}`}
>
<InfoIcon />
</IconButton>
}
/>
))
}
You should use a component, which create a unique state for each element, i wrote an easy to understand example.
import React, { useState } from "react"
const items = [
{
title: 'Card1',
price: 100
},
{
title: 'Card2',
price: 50
},
{
title: 'Card3',
price: 200
},
]
export default function App() {
return (
<>
{
items.map(element => {
return(
<Card {...element}/>
)
})
}
</>
)
}
function Card({title, price, key}) {
const [isHovered, setHover] = useState(false)
return (
<>
<div
key={key}
onMouseOver={() => {setHover(true)}}
onMouseOut={() => {setHover(false)}}
>
<div>
{title}
</div>
<h3>
{
isHovered && price
}
</h3>
</div>
</>
);
}
I made the card price to show if hovered so you can see it works on each individual component.
If you want the state to be in the parent without going all the way to an array or object, you can use a number instead. If only one item at a time is going to be active, you can just use the index of the active item as the state:
const { useState } = React;
const things = ["foo", "bar", "baz"];
function Component() {
const [active, setActive] = useState(-1);
const updateActivity = (index) => setActive(index === active ? -1 : index);
return (
<ul>
{things.map((thing, index) => (
<li>
<button key={index} onClick={() => updateActivity(index)}>
{index === active
? <strong>{thing}</strong>
: thing}
</button>
</li>
))}
<li>Value: {active}</li>
</ul>
);
}
ReactDOM.render(
<Component />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Alternatively, in cases where you want multiple items to be simultaneously active, you can use a "bit flag" approach where each bit of the value represents whether or not the corresponding index is active:
const { useState } = React;
const things = ["foo", "bar", "baz"];
function Component() {
const [active, setActive] = useState(0);
const updateActivity = (index) => setActive(active ^ Math.pow(2, index));
return (
<ul>
{things.map((thing, index) => (
<li>
<button key={index} onClick={() => updateActivity(index)}>
{active & Math.pow(2, index)
? <strong>{thing}</strong>
: thing}
</button>
</li>
))}
<li>Value: {active} ({active.toString(2).padStart(3, "0")})</li>
</ul>
);
}
ReactDOM.render(
<Component />,
document.getElementById("react2")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="react2"></div>