diff --git a/src/hub.js b/src/hub.js
index 6c6d9fc89c51675a67d67f6113506d1c1e692c56..f73dbb8b41ce5d2a8427a944a33bf0f5771995f1 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -102,6 +102,7 @@ AFRAME.registerInputMappings(inputConfig, true);
 
 const store = new Store();
 const concurrentLoadDetector = new ConcurrentLoadDetector();
+const uiRootProps = {};
 
 concurrentLoadDetector.start();
 
@@ -212,9 +213,7 @@ function mountUI(scene) {
   const htmlPrefix = document.body.dataset.htmlPrefix || "";
   const showProfileEntry = !store.state.profile.has_saved_profile;
 
-  // TODO: Refactor to avoid using return value
-  /* eslint-disable react/no-render-return-value */
-  const uiRoot = ReactDOM.render(
+  ReactDOM.render(
     <UIRoot
       {...{
         scene,
@@ -226,14 +225,12 @@ function mountUI(scene) {
         enableScreenSharing,
         store,
         htmlPrefix,
-        showProfileEntry
+        showProfileEntry,
+        ...uiRootProps
       }}
     />,
     document.getElementById("ui-root")
   );
-  /* eslint-enable react/no-render-return-value */
-
-  return uiRoot;
 }
 
 const onReady = async () => {
@@ -243,18 +240,24 @@ const onReady = async () => {
 
   registerNetworkSchemas();
 
-  const uiRoot = mountUI(scene);
+  mountUI(scene);
+
+  const remountUI = () => {
+    mountUI(scene);
+  };
 
   getAvailableVREntryTypes().then(availableVREntryTypes => {
-    uiRoot.setState({ availableVREntryTypes });
-    uiRoot.handleForcedVREntryType();
+    console.log('BPDEBUG availableVREntryTypes', availableVREntryTypes);
+    uiRootProps.availableVREntryTypes = availableVREntryTypes;
+    remountUI();
   });
 
   const environmentRoot = document.querySelector("#environment-root");
 
   const initialEnvironmentEl = document.createElement("a-entity");
   initialEnvironmentEl.addEventListener("bundleloaded", () => {
-    uiRoot.setState({ initialEnvironmentLoaded: true });
+    uiRootProps.initialEnvironmentLoaded = true;
+    remountUI();
     // Wait a tick plus some margin so that the environments actually render.
     setTimeout(() => scene.renderer.animate(null), 100);
   });
@@ -262,7 +265,8 @@ const onReady = async () => {
 
   if (qs.room) {
     // If ?room is set, this is `yarn start`, so just use a default environment and query string room.
-    uiRoot.setState({ janusRoomId: qs.room && !isNaN(parseInt(qs.room)) ? parseInt(qs.room) : 1 });
+    uiRootProps.janusRoomId = qs.room && !isNaN(parseInt(qs.room)) ? parseInt(qs.room) : 1;
+    remountUI();
     initialEnvironmentEl.setAttribute("gltf-bundle", {
       src: "https://asset-bundles-prod.reticulum.io/rooms/meetingroom/MeetingRoom.bundle.json"
       // src: "https://asset-bundles-prod.reticulum.io/rooms/theater/TheaterMeshes.bundle.json"
@@ -279,7 +283,8 @@ const onReady = async () => {
   const hub = data.hubs[0];
   const defaultSpaceTopic = hub.topics[0];
   const gltfBundleUrl = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle").src;
-  uiRoot.setState({ janusRoomId: defaultSpaceTopic.janus_room_id });
+  uiRootProps.janusRoomId = defaultSpaceTopic.janus_room_id;
+  remountUI();
   initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${gltfBundleUrl}`);
 };
 
diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js
index 07488ced3d668b051c21cd1274416b647654c69a..e89cc82a343652784c011ad4097ed9ce7357da02 100644
--- a/src/react-components/ui-root.js
+++ b/src/react-components/ui-root.js
@@ -61,11 +61,13 @@ class UIRoot extends Component {
     store: PropTypes.object,
     scene: PropTypes.object,
     htmlPrefix: PropTypes.string,
-    showProfileEntry: PropTypes.bool
+    showProfileEntry: PropTypes.bool,
+    availableVREntryTypes: PropTypes.object,
+    initialEnvironmentLoaded: PropTypes.bool,
+    janusRoomId: PropTypes.number
   };
 
   state = {
-    availableVREntryTypes: null,
     entryStep: ENTRY_STEPS.start,
     enterInVR: false,
 
@@ -88,12 +90,9 @@ class UIRoot extends Component {
     autoExitTimerInterval: null,
     secondsRemainingBeforeAutoExit: Infinity,
 
-    initialEnvironmentLoaded: false,
     exited: false,
 
-    showProfileEntry: false,
-
-    janusRoomId: null
+    showProfileEntry: false
   };
 
   constructor(props) {
@@ -110,8 +109,10 @@ class UIRoot extends Component {
     this.props.scene.addEventListener("stateremoved", this.onAframeStateChanged);
   }
 
-  componentWillUnmount() {
-    this.props.scene.removeEventListener("loaded", this.onSceneLoaded);
+  componentDidUpdate(prevProps) {
+    if (this.props.availableVREntryTypes && prevProps.availableVREntryTypes !== this.props.availableVREntryTypes) {
+      this.handleForcedVREntryType();
+    }
   }
 
   onSceneLoaded = () => {
@@ -256,7 +257,7 @@ class UIRoot extends Component {
   };
 
   enterGearVR = async () => {
-    if (this.state.availableVREntryTypes.gearvr === VR_DEVICE_AVAILABILITY.yes) {
+    if (this.props.availableVREntryTypes.gearvr === VR_DEVICE_AVAILABILITY.yes) {
       await this.performDirectEntryFlow(true);
     } else {
       this.exit();
@@ -275,7 +276,7 @@ class UIRoot extends Component {
   };
 
   enterDaydream = async () => {
-    if (this.state.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe) {
+    if (this.props.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe) {
       this.exit();
 
       // We are not in mobile chrome, so launch into chrome via an Intent URL
@@ -473,7 +474,7 @@ class UIRoot extends Component {
   };
 
   onAudioReadyButton = () => {
-    this.props.enterScene(this.state.mediaStream, this.state.enterInVR, this.state.janusRoomId);
+    this.props.enterScene(this.state.mediaStream, this.state.enterInVR, this.props.janusRoomId);
 
     const mediaStream = this.state.mediaStream;
 
@@ -498,7 +499,7 @@ class UIRoot extends Component {
   };
 
   render() {
-    if (!this.state.initialEnvironmentLoaded || !this.state.availableVREntryTypes || !this.state.janusRoomId) {
+    if (!this.props.initialEnvironmentLoaded || !this.props.availableVREntryTypes || !this.props.janusRoomId) {
       return (
         <IntlProvider locale={lang} messages={messages}>
           <div className="loading-panel">
@@ -552,21 +553,21 @@ class UIRoot extends Component {
       this.state.entryStep === ENTRY_STEPS.start ? (
         <div className="entry-panel">
           <TwoDEntryButton onClick={this.enter2D} />
-          {this.state.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && (
+          {this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && (
             <GenericEntryButton onClick={this.enterVR} />
           )}
-          {this.state.availableVREntryTypes.gearvr !== VR_DEVICE_AVAILABILITY.no && (
+          {this.props.availableVREntryTypes.gearvr !== VR_DEVICE_AVAILABILITY.no && (
             <GearVREntryButton onClick={this.enterGearVR} />
           )}
-          {this.state.availableVREntryTypes.daydream !== VR_DEVICE_AVAILABILITY.no && (
+          {this.props.availableVREntryTypes.daydream !== VR_DEVICE_AVAILABILITY.no && (
             <DaydreamEntryButton
               onClick={this.enterDaydream}
               subtitle={
-                this.state.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe ? daydreamMaybeSubtitle : ""
+                this.props.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe ? daydreamMaybeSubtitle : ""
               }
             />
           )}
-          {this.state.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && (
+          {this.props.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && (
             <div className="entry-panel__secondary" onClick={this.enterVR}>
               <FormattedMessage id="entry.cardboard" />
             </div>