Всплывающее меню с использованием контекста реагирования повторно открывается при закрытии его кнопкой

Я пишу 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.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Новый ответ

Вы были правы, разделение на две идемпотентные функции - 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>
  );
}

Ваша песочница мне почему-то не подошла, поэтому я создал новую с нуля и скопировал код, но в любом случае вот пример рабочей песочницы

Бывший ответ

У вас есть два варианта: один довольно сложный, а другой очень простой:

  • либо вы каким-то образом поместили обработчик mousedown на что-то, что охватывает все, включая кнопку,
  • или вы можете просто разделить toggleFlyout на две идемпотентные функции.

Вы также можете полностью пропустить флаг open, поскольку он всегда идет вместе с именем, поэтому, если имя отсутствует, это означает, что всплывающее окно не открыто:

function openFlyout(name) {
  setFlyout(name);
}

function closeFlyout() {
  setFlyout(null);
}

return (
  <FlyoutContext.Provider value = {{flyout, openFlyout, closeFlyout}}>
    {children}
  </FlyoutContext.Provider>
);

Дело в том, что если я разделю функцию переключения, кнопка все равно будет иметь атрибут openFlyout. И когда я хочу закрыть всплывающее окно, оно все равно действует дважды и снова открывается.

L_nnd 31.05.2024 18:36

Ой, подождите, так это что-то вроде кнопок на верхней панели StackOverflow справа от аватара, которые открывают входящие и последние достижения? Нажатие кнопки открывает окно, повторное нажатие кнопки закрывает окно, но также щелчок в любом месте за пределами поля также закрывает окно - это то, чего вы собираетесь добиться? Просто удалите обработчик с кнопки, когда всплывающее окно открыто: onClick = {!flyout && () => toggleFlyout("UserFlyout")} (рекомендуется обернуть это в именованную функцию)

Iorweth333 01.06.2024 19:10

На самом деле это внешний щелчок, который переключается перед нажатием кнопки. Итак, в случае вашего кода он все равно не работает, поскольку считает, что всплывающее окно закрыто, и открывает его повторно. Есть ли способ использовать event.stopPropagation() в flyoutManager. Я видел это в другом вопросе, но не могу понять, как использовать его в своем коде, потому что он просто выходит из строя.

L_nnd 02.06.2024 23:21

Попробуйте создать рабочую демо-версию (например, на CodeSandbox). Будет проще, если я смогу с этим поиграться

Iorweth333 03.06.2024 11:30

Дружище, извини, я не знаю CodeSandBox или любого другого подобного сайта. Я думал, что это сработает, но у меня проблема в App.js с const { toggleFlyout } = useFlyout();, но он работает на рабочем столе. В любом случае вот ссылка: codeandbox.io/p/sandbox/flyout-mgp9ry

L_nnd 03.06.2024 21:41

> Песочница не найдена. Вероятно, песочница, к которой вы пытаетесь получить доступ, не существует или у вас нет необходимых разрешений для доступа к ней.

Iorweth333 04.06.2024 11:31

Вот и все, теперь у вас должен быть доступ

L_nnd 04.06.2024 18:03

ОК, я отредактировал свой ответ, проверка снята

Iorweth333 06.06.2024 12:39

Замечательный ! Большое спасибо за вашу драгоценную помощь!

L_nnd 06.06.2024 21:39

Другие вопросы по теме