diff --git a/web/components/menu.react.js b/web/components/menu.react.js
--- a/web/components/menu.react.js
+++ b/web/components/menu.react.js
@@ -3,6 +3,7 @@
 import classnames from 'classnames';
 import * as React from 'react';
 
+import { useRenderMenu } from '../menu-provider.react';
 import css from './menu.css';
 
 type MenuVariant = 'thread-actions' | 'member-actions';
@@ -11,53 +12,110 @@
   +icon: React.Node,
   +children?: React.Node,
   +variant?: MenuVariant,
+  +onChange?: boolean => void,
 };
 
 function Menu(props: MenuProps): React.Node {
-  const [isOpen, setIsOpen] = React.useState(false);
+  const buttonRef = React.useRef();
+  const {
+    renderMenu,
+    setMenuPosition,
+    closeMenu,
+    setCurrentOpenMenu,
+    currentOpenMenu,
+  } = useRenderMenu();
+  const { icon, children, variant = 'thread-actions', onChange } = props;
+  const ourSymbol = React.useRef(Symbol());
+  const menuActionListClasses = React.useMemo(
+    () =>
+      classnames(css.menuActionList, {
+        [css.menuActionListThreadActions]: variant === 'thread-actions',
+        [css.menuActionListMemberActions]: variant === 'member-actions',
+      }),
+    [variant],
+  );
 
-  const { icon, children, variant = 'thread-actions' } = props;
+  const menuActionList = React.useMemo(
+    () => <div className={menuActionListClasses}>{children}</div>,
+    [children, menuActionListClasses],
+  );
 
-  const closeMenuCallback = React.useCallback(() => {
-    document.removeEventListener('click', closeMenuCallback);
-    if (isOpen) {
-      setIsOpen(false);
+  const isOurMenuOpen = currentOpenMenu === ourSymbol.current;
+
+  const updatePosition = React.useCallback(() => {
+    if (buttonRef.current && isOurMenuOpen) {
+      const { top, left } = buttonRef.current.getBoundingClientRect();
+      setMenuPosition({ top, left });
+    }
+  }, [isOurMenuOpen, setMenuPosition]);
+
+  React.useEffect(() => {
+    if (!window) {
+      return undefined;
     }
-  }, [isOpen]);
+
+    window.addEventListener('resize', updatePosition);
+    return () => window.removeEventListener('resize', updatePosition);
+  }, [updatePosition]);
+
+  // useLayoutEffect is necessary so that the menu position is immediately
+  // updated in the first render of component
+  React.useLayoutEffect(() => {
+    updatePosition();
+  }, [updatePosition]);
+
+  const closeMenuCallback = React.useCallback(() => {
+    closeMenu(menuActionList);
+  }, [closeMenu, menuActionList]);
 
   React.useEffect(() => {
-    if (!document || !isOpen) {
+    onChange?.(isOurMenuOpen);
+  }, [isOurMenuOpen, onChange]);
+
+  React.useEffect(() => {
+    if (!isOurMenuOpen) {
       return undefined;
     }
     document.addEventListener('click', closeMenuCallback);
-    return () => document.removeEventListener('click', closeMenuCallback);
-  }, [closeMenuCallback, isOpen]);
+    return () => {
+      document.removeEventListener('click', closeMenuCallback);
+    };
+  }, [closeMenuCallback, isOurMenuOpen]);
 
-  const switchMenuCallback = React.useCallback(() => {
-    setIsOpen(isMenuOpen => !isMenuOpen);
-  }, []);
+  const prevActionListRef = React.useRef<React.Node>(null);
+  React.useEffect(() => {
+    if (!isOurMenuOpen) {
+      prevActionListRef.current = null;
+      return;
+    }
+    if (prevActionListRef.current === menuActionList) {
+      return;
+    }
+    renderMenu(menuActionList);
 
-  if (React.Children.count(children) === 0) {
-    return null;
-  }
+    prevActionListRef.current = menuActionList;
+  }, [isOurMenuOpen, menuActionList, renderMenu]);
+
+  React.useEffect(() => {
+    return () => closeMenu(prevActionListRef.current);
+  }, [closeMenu]);
 
-  let menuActionList = null;
-  if (isOpen) {
-    const menuActionListClasses = classnames(css.menuActionList, {
-      [css.menuActionListThreadActions]: variant === 'thread-actions',
-      [css.menuActionListMemberActions]: variant === 'member-actions',
-    });
+  const onClickMenuCallback = React.useCallback(() => {
+    setCurrentOpenMenu(ourSymbol.current);
+  }, [setCurrentOpenMenu]);
 
-    menuActionList = <div className={menuActionListClasses}>{children}</div>;
+  if (React.Children.count(children) === 0) {
+    return null;
   }
 
   return (
-    <div>
-      <button className={css.menuButton} onClick={switchMenuCallback}>
-        {icon}
-      </button>
-      {menuActionList}
-    </div>
+    <button
+      ref={buttonRef}
+      className={css.menuButton}
+      onClick={onClickMenuCallback}
+    >
+      {icon}
+    </button>
   );
 }