I have Modal
component with info boxes. In each InfoBox
a button (icon) reveals more info about a certain ingredient of the product. That functionality is contained within each InfoBox
component.
When you click the button (icon) in one InfoBox, I want all other InfoBox(es) to close - how can I control sibling elements' behaviour using refs?
I'm trying to:
useImperativeHandle
call the closeShowMore
function in all other refs.But I don't know how to mark that particular ref in the refarray where the button has been clicked.
Thanks in advance for your time!
PARENT:
const Modal = forwardRef(({ beer }, ref) => {
// stuff removed for brev
const [infoRef, setInfoRef] = useState([]);
const closeInfoBox = (valuePassedfromChildsEvent) => {
// here I would loop through infoRef, find the ref in which the button was clicked and then close the rest through useImperativeHandle()
};
useEffect(() => {
// create an array of refs for infoBoxes
setInfoRef(
Array(6)
.fill()
.map((_, i) => infoRef[i] || createRef())
);
}, []);
if (display) {
return (
<div className="modal-wrapper">
<div ref={backgroundRef} className="modal-background"></div>
<div className="modal-box">
<div className="info-box-wrapper">
<InfoBox
ref={infoRef[0]}
name="abv"
value={abv}
imageUrl={"../../assets/abv-white-2.svg"}
showMoreActive={closeInfoBox}
/>
<InfoBox
ref={infoRef[1]}
name="ibu"
value={ibu}
imageUrl={"../../assets/ibu-white.svg"}
showMoreActive={closeInfoBox}
/>
<InfoBox
ref={infoRef[2]}
name="fg"
value={target_fg}
imageUrl={"../../assets/style-white.svg"}
showMoreActive={closeInfoBox}
/>
// a few more of these
</div>
</div>
</div>
);
}
return null;
});
export default Modal;
CHILD:
const InfoBox = forwardRef((props, ref) => {
const [showMore, setShowMore] = useState(false);
const { name, value, imageUrl, showMoreActive } = props;
useImperativeHandle(ref, () => ({
openShowMore,
closeShowMore,
// is there a way to add an 'isActive' state/var here so I can access this from the loop in the parent and say if(!infoRef.current.isActive) {closeShowMore()}?
}));
const openShowMore = () => {
setShowMore(true);
showMoreActive(showMore) // I tried sending up this state value to parent to mark the active child but this always logs false when passed to parent, even when true. why?
};
const closeShowMore = () => {
setShowMore(false);
};
return (
<div className={`${name}`}>
<div className="info-box-container">
<div className="info-box-container-top">
<img src={imageUrl} alt="icon" />
<div className="info-wrapper">
<h4>{name}</h4>
<h5>{value}</h5>
</div>
<div
className="plus-minus-icon-wrapper"
onClick={() => openShowMore()}
>
{!showMore ? <GoPlus /> : <FiMinus />}
</div>
</div>
{showMore && (
<div className="info-box-conetiner-bottom">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Officia
optio ab ullam! Porro molestias accusantium et laborum distinctio
</p>
</div>
)}
</div>
</div>
);
});
export default InfoBox;
You are making it unnecessarily complicated. What you want is to use as little state as possible, and to keep that state in the smallest common parent for all affected components.
In your case, what you want to do is to have a state variabel in Modal that goes something like this:
const [expandedInfoBoxName, setExpandedInfoBoxName] = useState(null);
You pass these props down to your InfoBox:
<InfoBox
name="ibu"
{your other props}
expandInfoBox={() => setExpandedInfoBoxName("ibu")
showMore={expandedInfoBoxName === "ibu"}
/>
In your InfoBox you remove your state and the functions useImperativeHandle, openShowMore and closeShowMore. Add this to get your new props:
const { name, value, imageUrl, expandInfoBox, showMore } = props;
Then in your jsx you do:
<div
className="plus-minus-icon-wrapper"
onClick={expandInfoBox}
>
That's it. In short, you do not want to spread out your state. Always ask yourself what the minimum state you need to describe something is.