I made a menu to display options. The code was working fine before. I copied it from a udemy course. He used the same code. Now all of sudden it is not working anymore. It keeps on hiding the menu whenever I try to open it. I cant seem to find the bug. I have added some css classes like animate-scale for animation.
Header.js
import React, { useEffect, useRef, useState } from "react";
import { AiOutlinePlus } from "react-icons/ai";
import {
BsFillNodePlusFill,
BsFillSunFill,
BsNodeMinusFill,
} from "react-icons/bs";
import { useTheme } from "../hooks";
export default function Header({ onAddMovieClick, onAddActorClick }) {
const [showOptions, setShowOptions] = useState(false);
const { toggleTheme } = useTheme();
const options = [
{
title: "Add Movie",
onClick: onAddMovieClick,
},
{
title: "Add Actor",
onClick: onAddActorClick,
},
];
return (
<div className="flex justify-between items-center relative">
<input
type="text"
className="border-2 border-light-subtle dark:border-dark-subtle outline-none bg-transparent focus:border-primary dark:border-white transition p-1 rounded"
placeholder="Search Movies..."
/>
<div className="flex items-center space-x-3">
<button
onClick={toggleTheme}
className="dark:text-white text-light-subtle p-1 rounded "
>
<BsFillSunFill size={24} />
</button>
{/* <div className="relative"> */}
<button
onClick={() => setShowOptions(true)}
className="flex space-x-2 items-center border-secondary text-secondary border-2 font-semibold text-lg px-3 rounded"
>
<span>Create</span>
<AiOutlinePlus />
</button>
</div>
<CreateOptions
visible={showOptions}
onClose={() => setShowOptions(false)}
// onClose={() => setShowOptions(false)}
options={options}
/>
</div>
// </div>
);
}
const CreateOptions = ({ visible, onClose, options }) => {
const container = useRef();
const containerID = "option-container";
useEffect(() => {
const handleClose = (e) => {
if (!visible) return;
const { parentElement, id } = e.target;
if (parentElement.id === containerID || id === containerID) return;
container.current.classList.remove("animate-scale");
container.current.classList.add("animate-scale-reverse");
};
document.addEventListener("click", handleClose);
return () => {
document.removeEventListener("click", handleClose);
};
}, [visible]);
const handleAnimationEnd = (e) => {
if (e.target.classList.contains("animate-scale-reverse")) {
console.log("triggered");
onClose();
}
e.target.classList.remove("animate-scale");
};
if (!visible) return null;
return (
<div
id={containerID}
ref={container}
onAnimationEnd={handleAnimationEnd}
className="absolute top-12 right-1 drop-shadow-lg bg-white flex flex-col p-5 space-y-3 dark:bg-secondary animate-scale"
>
{options.map(({ title, onClick }) => (
<Option key={title} onClick={onClick}>
{title}
</Option>
))}
{/* <Option>Add Movie</Option> */}
{/* <Option>Add Actor</Option> */}
</div>
);
};
const Option = ({ children, onClick }) => {
return (
<button onClick={onClick} className="text-secondary dark:text-white">
{children}
</button>
);
};
index.css
.animate-scale {
transform-origin: top;
animation: scale 0.2s;
}
.animate-scale-reverse {
transform-origin: top;
animation: scale 0.2s reverse forwards;
}
@keyframes scale {
0% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
I have shared it on codeSandBox. Just so one can see the bug.
https://codesandbox.io/s/practical-jackson-6xut0y?file=/src/App.js
CodePudding user response:
I did some fixes in your codesandbox. Let me explain what I did.
The problem is that a click on button propagates down to your custom component and it catches a click event, which closes your modal. To fix that, use e.stopPropagation();
on your button.
Also, seems like you were trying to close the modal, when clicking outside of it (where you checked parent id). A better way to catch a click outside of a component is by using container.current && !container.current.contains(e.target)
, where container
is the ref
, that you've created. In this condition check, you are checking, if the clicked target is inside of your component or not.
CodePudding user response:
I took a look at your codesandbox link and it looks like this line in your useEffect is causing the problem:
container.current.classList.add("animate-scale-reverse")
By the looks of how your function is set up, that function is being called each time you click the button its automatically adding the reverse class which will close the modal.
CodePudding user response:
I think that your problem occurs in useEffect
in CreateOptions
component, You are setting an event listener on the whole document for closing the options dropdown but keep in mind that the button that opens that dropdown is also part of the document so as soon as you click to open the options dropdown, the document also sees that there is a click and a click
listener for the whole document and ultimately closes the options dropdown
I hope you can understand what I am trying to say
Here is a hook that I found on the internet that can detect click outside of an element > https://usehooks.com/useOnClickOutside/
function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
},
[ref, handler]
);
}
You can use the hook like so,
useOnClickOutside(ref, onClickHander)
the ref is for the element that you want to detect click outside of, In your case this would be the options dropdown
the the click hander is the function which runs when a click is detected, In your case this would be handleClose
Hope my answer was helpful to you, If not then my apologies for wasting your time, Ayan Ali