Я пишу FlyoutManager, из которого я могу переключать все всплывающие меню в приложении реагирования. Я пытаюсь заставить свойство закрываться при нажатии снаружи. В другом вопросе о stackoverflow я обнаружил, что моя функция щелчка снаружи срабатывает, когда я нажимаю кнопку, чтобы закрыть ее, поэтому она повторно открывает всплывающее меню, поскольку оно дважды переключает состояние меню.
Есть ли у вас идеи, как решить мою проблему?
Вот код заголовка, упрощенный для моей проблемы:
export const Header = () => {
const {toggleFlyout} = useFlyout();
return (
<header>
<div>
/*
...
Other components
*/
<nav>
/*
...
Other buttons
*/
<button id = "UserFlyout" onClick = {() => toggleFlyout("UserFlyout")}>
User-logo
</button>
</nav>
</div>
</header>
);
};
Вот папка FlyoutContext, содержащая функцию переключения:
export const FlyoutContext = createContext();
export const FlyoutProvider = ({children}) => {
const [flyout, setFlyout] = useState(null);
const [isOpen, setIsOpen] = useState(false)
const toggleFlyout = name => {
setIsOpen(!isOpen);
if (!isOpen) {
setFlyout(name);
} else {
setFlyout(null);
}
}
return (
<FlyoutContext.Provider value = {{flyout, toggleFlyout}}>
{children}
</FlyoutContext.Provider>
);
};
export const useFlyout = () => useContext(FlyoutContext);
Вот папка FlyoutManager
const FlyoutList = {
UserFlyout: UserFlyout
};
export const FlyoutManager = () => {
const {flyout, toggleFlyout} = useFlyout();
const flyoutRef = useRef(null);
const Flyout = FlyoutList[flyout];
useEffect(() => {
const handler = event => {
if (flyoutRef.current !== null && !flyoutRef.current.contains(event.target)) {
toggleFlyout(flyout);
}
};
document.addEventListener('mousedown', handler);
return () => {
document.removeEventListener('mousedown', handler);
};
}, [flyoutRef, flyout, toggleFlyout]);
if (!flyout) return null;
return (
<Flyout flyoutRef = {flyoutRef}>
<div ref = {flyoutRef}>
user flyout
</div>
</Flyout>
);
};
Большое спасибо за вашу помощь
Некоторые идеи могут заключаться в том, чтобы проверить, не является ли целью события mousedown кнопка. Но поскольку я использую контекст, я не могу получить доступ к кнопке id во FlyoutContext.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы были правы, разделение на две идемпотентные функции - openFlyout и closeFlyout - не было полным ответом.
Вместо этого я добавил ссылку на кнопку в контекст и проверил, была ли нажата эта кнопка. Вот как useEffect выглядит сейчас:
useEffect(() => {
const handler = (event) => {
const isOutsideFlyout = !flyoutRef.current?.contains(event.target);
const isOutsideButton = !flyoutButonRef.current?.contains(event.target);
if (isOutsideFlyout && isOutsideButton) {
closeFlyout();
}
};
document.addEventListener("mousedown", handler);
return () => {
document.removeEventListener("mousedown", handler);
};
}, [flyoutRef, flyout, closeFlyout]);
(? в flyoutRef.current?.contains — это Необязательный оператор цепочки, делает условие короче)
И вот кнопка:
import { useFlyout } from "./FlyoutContext";
export default function FlyoutButton() {
const { toggleFlyout, flyoutButonRef } = useFlyout();
return (
<button ref = {flyoutButonRef} onClick = {() => toggleFlyout("UserFlyout")}>
UserFlyout
</button>
);
}
Ваша песочница мне почему-то не подошла, поэтому я создал новую с нуля и скопировал код, но в любом случае вот пример рабочей песочницы
У вас есть два варианта: один довольно сложный, а другой очень простой:
toggleFlyout на две идемпотентные функции.Вы также можете полностью пропустить флаг open, поскольку он всегда идет вместе с именем, поэтому, если имя отсутствует, это означает, что всплывающее окно не открыто:
function openFlyout(name) {
setFlyout(name);
}
function closeFlyout() {
setFlyout(null);
}
return (
<FlyoutContext.Provider value = {{flyout, openFlyout, closeFlyout}}>
{children}
</FlyoutContext.Provider>
);
Ой, подождите, так это что-то вроде кнопок на верхней панели StackOverflow справа от аватара, которые открывают входящие и последние достижения? Нажатие кнопки открывает окно, повторное нажатие кнопки закрывает окно, но также щелчок в любом месте за пределами поля также закрывает окно - это то, чего вы собираетесь добиться? Просто удалите обработчик с кнопки, когда всплывающее окно открыто: onClick = {!flyout && () => toggleFlyout("UserFlyout")} (рекомендуется обернуть это в именованную функцию)
На самом деле это внешний щелчок, который переключается перед нажатием кнопки. Итак, в случае вашего кода он все равно не работает, поскольку считает, что всплывающее окно закрыто, и открывает его повторно. Есть ли способ использовать event.stopPropagation() в flyoutManager. Я видел это в другом вопросе, но не могу понять, как использовать его в своем коде, потому что он просто выходит из строя.
Попробуйте создать рабочую демо-версию (например, на CodeSandbox). Будет проще, если я смогу с этим поиграться
Дружище, извини, я не знаю CodeSandBox или любого другого подобного сайта. Я думал, что это сработает, но у меня проблема в App.js с const { toggleFlyout } = useFlyout();, но он работает на рабочем столе. В любом случае вот ссылка: codeandbox.io/p/sandbox/flyout-mgp9ry
> Песочница не найдена. Вероятно, песочница, к которой вы пытаетесь получить доступ, не существует или у вас нет необходимых разрешений для доступа к ней.
Вот и все, теперь у вас должен быть доступ
ОК, я отредактировал свой ответ, проверка снята
Замечательный ! Большое спасибо за вашу драгоценную помощь!
Дело в том, что если я разделю функцию переключения, кнопка все равно будет иметь атрибут openFlyout. И когда я хочу закрыть всплывающее окно, оно все равно действует дважды и снова открывается.