diff --git a/web/app.react.js b/web/app.react.js
--- a/web/app.react.js
+++ b/web/app.react.js
@@ -1,16 +1,15 @@
 // @flow
 
+import { config as faConfig } from '@fortawesome/fontawesome-svg-core';
+import invariant from 'invariant';
 import '@fontsource/inter';
 import '@fontsource/inter/500.css';
 import '@fontsource/inter/600.css';
-
 import '@fontsource/ibm-plex-sans';
 import '@fontsource/ibm-plex-sans/500.css';
 import '@fontsource/ibm-plex-sans/600.css';
-
 import 'basscss/css/basscss.min.css';
 import './theme.css';
-import { config as faConfig } from '@fortawesome/fontawesome-svg-core';
 import _isEqual from 'lodash/fp/isEqual';
 import * as React from 'react';
 import { DndProvider } from 'react-dnd';
@@ -35,6 +34,7 @@
 import Chat from './chat/chat.react';
 import InputStateContainer from './input/input-state-container.react';
 import LoadingIndicator from './loading-indicator.react';
+import { ModalContext } from './modals/modal-provider.react';
 import DisconnectedBar from './redux/disconnected-bar';
 import DisconnectedBarVisibilityHandler from './redux/disconnected-bar-visibility-handler';
 import FocusHandler from './redux/focus-handler.react';
@@ -83,6 +83,7 @@
   +activeThreadCurrentlyUnread: boolean,
   // Redux dispatch functions
   +dispatch: Dispatch,
+  +setModal: (?React.Node) => void,
 };
 type State = {
   +modal: ?React.Node,
@@ -182,7 +183,7 @@
             <div className={css['main-content']}>{mainContent}</div>
           </div>
         </InputStateContainer>
-        <LeftLayoutAside setModal={this.setModal} />
+        <LeftLayoutAside setModal={this.props.setModal} />
       </div>
     );
   }
@@ -227,6 +228,11 @@
 
     const dispatch = useDispatch();
 
+    const modalContext = React.useContext(ModalContext);
+    invariant(modalContext, 'ModalContext not found');
+
+    const { setModal } = modalContext;
+
     return (
       <App
         {...props}
@@ -236,6 +242,7 @@
         mostRecentReadThread={mostRecentReadThread}
         activeThreadCurrentlyUnread={activeThreadCurrentlyUnread}
         dispatch={dispatch}
+        setModal={setModal}
       />
     );
   },
diff --git a/web/modals/modal-provider.react.js b/web/modals/modal-provider.react.js
new file mode 100644
--- /dev/null
+++ b/web/modals/modal-provider.react.js
@@ -0,0 +1,37 @@
+// @flow
+
+import * as React from 'react';
+type Props = {
+  +children: React.Node,
+};
+type ModalContextType = {
+  +modal: ?React.Node,
+  +setModal: (?React.Node) => void,
+  +clearModal: () => void,
+};
+
+const ModalContext: React.Context<?ModalContextType> = React.createContext<?ModalContextType>(
+  {
+    modal: null,
+    setModal: () => {},
+    clearModal: () => {},
+  },
+);
+
+function ModalProvider(props: Props): React.Node {
+  const { children } = props;
+  const [modal, setModal] = React.useState(null);
+  const clearModal = React.useCallback(() => setModal(null), [setModal]);
+
+  const handleClick = React.useCallback((component: ?React.Node) => {
+    setModal(component);
+  }, []);
+
+  return (
+    <ModalContext.Provider value={{ modal, clearModal, setModal: handleClick }}>
+      {children}
+    </ModalContext.Provider>
+  );
+}
+
+export { ModalProvider, ModalContext };
diff --git a/web/root.js b/web/root.js
--- a/web/root.js
+++ b/web/root.js
@@ -9,6 +9,7 @@
 import { reduxLoggerMiddleware } from 'lib/utils/action-logger';
 
 import HotRoot from './hot';
+import { ModalProvider } from './modals/modal-provider.react';
 import { reducer } from './redux/redux-setup';
 import type { AppState, Action } from './redux/redux-setup';
 
@@ -21,7 +22,9 @@
 
 const RootProvider = (): React.Node => (
   <Provider store={store}>
-    <HotRoot />
+    <ModalProvider>
+      <HotRoot />
+    </ModalProvider>
   </Provider>
 );