I have this Item component that I am using in another component:
import React, { useState } from "react";
import Button from "./Button";
const Item = ({ name }) => {
const [isSelected, setIsSelected] = useState(false);
const toggle = () => {
setIsSelected(!isSelected);
};
var buttonColor;
var buttonText;
if (isSelected === true) {
buttonColor = "bg-button-blue";
buttonText = "Selected";
} else {
buttonColor = "bg-button-gray";
buttonText = "Select";
}
return (
<div onClick={toggle} className="flex ml-2 items-center">
<div className="text-misc-gray text-xs w-40">{name}</div>
<div>
<Button
text={buttonText}
color={buttonColor}
height={"h-8"}
width={"w-18"}
></Button>
</div>
</div>
);
};
export default Item;
In the other component, I have multiple instances of this Item component, representing different items. The Item component can with a click change property, like text and color for the button. The problem is that in the other component, multiple of these Items can be toggled at the same time.
I would like that out of every instance of the Item component in the other component, only a single one can be toggled on at the same time. So if I select one item, the previous (if any selected) will be "unselected", changing the text and color back to the original state.
Can this be solved by only making changes in the Item component, or do I also need to make changes where it's being imported?
CodePudding user response:
Can this be solved by only making changes in the Item component
No good way using the React paradigm, because you want one instance to affect another instance, where the other instance is not a child.
In the ancestor component of the <Item>
s, create a state variable that holds the index (or ID, or name, or some other uniquely identifying aspect) of the Item component currently toggled on, and then pass down an isSelected
prop to each Item, as well as the state setter. (The individual Items should no longer have an isSelected
state.) Perhaps something along the lines of:
const numItems = 5;
const Parent = () => {
const [selectedIndex, setSelectedIndex] = useState(-1);
const makeToggle = (i) => () => {
setSelectedIndex(i === selectedIndex ? -1 : i);
};
return (
<div>
{ Array.from(
{ length: numItems },
(_, i) => <Item
isSelected={selectedIndex == i}
toggle={makeToggle(i)}
/>
)}
</div>
);
};
CodePudding user response:
Can this be solved by only making changes in the Item component
Isolating state is good, but in this situation, Item
state has dependencies on other components, so we cannot isolate that state completely.
I'd suggest that you should lift your state isSelected
up to the parent component, and pass that state down to each Item
for UI update.
import React, { useState } from "react";
const ParentComponent = () => {
const [selectedIndex, setSelectedIndex] = useState();
//[...Array(5)] is representing your actual items
return (
<div>
[...Array(5)].map((value, index) => <Item key={index} isSelected={selectedIndex === index} index={index} toggle={(i) => setSelectedIndex(i)} />)
</div>
);
};
And then change Item
props with a little logic modification
import React, { useState } from "react";
import Button from "./Button";
const Item = ({ name, isSelected, index, toggle }) => {
var buttonColor;
var buttonText;
if (isSelected === true) {
buttonColor = "bg-button-blue";
buttonText = "Selected";
} else {
buttonColor = "bg-button-gray";
buttonText = "Select";
}
return (
<div onClick={() => toggle(index)} className="flex ml-2 items-center">
<div className="text-misc-gray text-xs w-40">{name}</div>
<div>
<Button
text={buttonText}
color={buttonColor}
height={"h-8"}
width={"w-18"}
></Button>
</div>
</div>
);
};
export default Item;