Repository: streamlit/streamlit
Pull request: Auto-reload page if Streamlit server version ≠ frontend version
**Issue:**  This PR fixes https://github.com/streamlit/streamlit/issues/536

**Description:**  Reloading frontend if Streamlit server version changes

---

**Contribution License Agreement**

By submiting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.


Problem statement:
Auto-reload page if Streamlit server version ≠ frontend version
When users upgrade Streamlit, they sometimes have a browser tab running the frontend for the previous version. If we changed the Streamlit protos in between versions, this will break that tab in weird ways.

Thankfully, we already send the server version to the frontend every time the websocket connects. So the frontend could just check that the version is correct, and autoreload the browser tab if there's a mismatch.

Hints from issue discussion:

Commit message: wrong test name

Patch to review:
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 5e6634aff4b1..c93e50c1292b 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -16,12 +16,22 @@
  */
 
 import React from "react"
-import ReactDOM from "react-dom"
-import { SessionInfo, Args as SessionInfoArgs } from "./lib/SessionInfo"
+import { mount, CommonWrapper } from "enzyme"
+import { ForwardMsg, Initialize } from "autogen/proto"
 import { MetricsManager } from "./lib/MetricsManager"
 import { getMetricsManagerForTest } from "./lib/MetricsManagerTestUtils"
+import { SessionInfo, Args as SessionInfoArgs } from "./lib/SessionInfo"
+
 import App from "./App"
 
+const getWrapper = (): CommonWrapper => {
+  const mountPoint = document.createElement("div")
+  mountPoint.setAttribute("id", "ConnectionStatus")
+  document.body.appendChild(mountPoint)
+
+  return mount(<App />, { attachTo: mountPoint })
+}
+
 describe("App", () => {
   beforeEach(() => {
     SessionInfo.current = new SessionInfo({
@@ -39,10 +49,28 @@ describe("App", () => {
   })
 
   it("renders without crashing", () => {
-    const mountPoint = document.createElement("div")
-    mountPoint.setAttribute("id", "ConnectionStatus")
-    document.body.appendChild(mountPoint)
-    ReactDOM.render(<App />, mountPoint)
-    ReactDOM.unmountComponentAtNode(mountPoint)
+    const wrapper = getWrapper()
+
+    expect(wrapper.html()).not.toBeNull()
+  })
+
+  it("should reload when streamlit server version changes", () => {
+    const wrapper = getWrapper()
+
+    window.location.reload = jest.fn()
+
+    const fwMessage = new ForwardMsg()
+    const initMessage = new Initialize()
+
+    initMessage.environmentInfo = {
+      streamlitVersion: "svv",
+    }
+
+    fwMessage.initialize = initMessage
+
+    // @ts-ignore
+    wrapper.instance().handleMessage(fwMessage)
+
+    expect(window.location.reload).toHaveBeenCalled()
   })
 })
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index b2d365497299..3540f2bdb9a0 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -185,6 +185,30 @@ class App extends PureComponent<Props, State> {
     MetricsManager.current.enqueue("viewReport")
   }
 
+  showError(title: string, errorNode: ReactNode): void {
+    logError(errorNode)
+    const newDialog: DialogProps = {
+      type: DialogType.WARNING,
+      title: title,
+      msg: errorNode,
+      onClose: () => {},
+    }
+    this.openDialog(newDialog)
+  }
+
+  isServerVersionUpdated(initializeMsg: Initialize): boolean {
+    if (SessionInfo.isSet()) {
+      const { streamlitVersion: currentStreamlitVersion } = SessionInfo.current
+      const {
+        streamlitVersion: newStreamlitVersion,
+      } = initializeMsg.environmentInfo
+
+      return currentStreamlitVersion === newStreamlitVersion
+    }
+
+    return true
+  }
+
   /**
    * Called by ConnectionManager when our connection state changes
    */
@@ -204,17 +228,6 @@ class App extends PureComponent<Props, State> {
     }
   }
 
-  showError(title: string, errorNode: ReactNode): void {
-    logError(errorNode)
-    const newDialog: DialogProps = {
-      type: DialogType.WARNING,
-      title: title,
-      msg: errorNode,
-      onClose: () => {},
-    }
-    this.openDialog(newDialog)
-  }
-
   /**
    * Callback when we get a message from the server.
    */
@@ -278,6 +291,12 @@ class App extends PureComponent<Props, State> {
    */
 
   handleInitialize(initializeMsg: Initialize): void {
+    if (!this.isServerVersionUpdated(initializeMsg)) {
+      window.location.reload()
+
+      return
+    }
+
     SessionInfo.current = new SessionInfo({
       streamlitVersion: initializeMsg.environmentInfo.streamlitVersion,
       pythonVersion: initializeMsg.environmentInfo.pythonVersion,
@@ -302,6 +321,8 @@ class App extends PureComponent<Props, State> {
 
     const initialState = initializeMsg.sessionState
     this.handleSessionStateChanged(initialState)
+
+    return
   }
 
   /**
diff --git a/frontend/src/components/core/Block/Block.tsx b/frontend/src/components/core/Block/Block.tsx
index d400c0809a0b..d4c77a39f2a5 100644
--- a/frontend/src/components/core/Block/Block.tsx
+++ b/frontend/src/components/core/Block/Block.tsx
@@ -184,8 +184,8 @@ class Block extends PureComponent<Props> {
     const className = Block.getClassNames(isStale, isEmpty)
 
     return (
-      <Maybe enable={enable}>
-        <div key={index} className={className} style={{ width }}>
+      <Maybe enable={enable} key={index}>
+        <div className={className} style={{ width }}>
           <ErrorBoundary width={width}>
             <Suspense
               fallback={
diff --git a/frontend/src/hocs/withFullScreenWrapper/withFullScreenWrapper.test.tsx b/frontend/src/hocs/withFullScreenWrapper/withFullScreenWrapper.test.tsx
index e1fa88395256..2873454ad620 100644
--- a/frontend/src/hocs/withFullScreenWrapper/withFullScreenWrapper.test.tsx
+++ b/frontend/src/hocs/withFullScreenWrapper/withFullScreenWrapper.test.tsx
@@ -35,7 +35,7 @@ const getProps = (props: object = {}): ReportElementProps => ({
   ...props,
 })
 
-describe("Button widget", () => {
+describe("withFullScreenWrapper HOC", () => {
   it("renders without crashing", () => {
     const props = getProps()
     const WithHoc = withFullScreenWrapper(testComponent)


Code review comments:
diff_hunk: @@ -185,6 +185,30 @@ class App extends PureComponent<Props, State> {
     MetricsManager.current.enqueue("viewReport")
   }
 
+  showError(title: string, errorNode: ReactNode): void {
+    logError(errorNode)
+    const newDialog: DialogProps = {
+      type: DialogType.WARNING,
+      title: title,
+      msg: errorNode,
+      onClose: () => {},
+    }
+    this.openDialog(newDialog)
+  }
+
+  isServerVersionUpdated(initializeMsg: Initialize): boolean {
path: frontend/src/App.tsx
text: @user1:
Please, canyou add docstring to this func and maybe check the name? It's hard to understand :)