Procházet zdrojové kódy

Move initial state to separate file (#1599)

Masen Furer před 1 rokem
rodič
revize
afcbe7e5a6

+ 1 - 4
integration/test_dynamic_routes.py

@@ -145,11 +145,8 @@ def test_on_load_navigate(dynamic_route: AppHarness, driver):
     # look up the backend state and assert that `on_load` was called for all
     # look up the backend state and assert that `on_load` was called for all
     # navigation events
     # navigation events
     backend_state = dynamic_route.app_instance.state_manager.states[token]
     backend_state = dynamic_route.app_instance.state_manager.states[token]
-    # TODO: navigating to dynamic page initially fires hydrate twice
-    # because the new page re-initializes `useEventLoop`, with the same hydrate event
-    # but routeChangeComplete also still fires.
     time.sleep(0.2)
     time.sleep(0.2)
-    assert backend_state.order[-10:] == [str(ix) for ix in range(10)]
+    assert backend_state.order == [str(ix) for ix in range(10)]
 
 
 
 
 def test_on_load_navigate_non_dynamic(dynamic_route: AppHarness, driver):
 def test_on_load_navigate_non_dynamic(dynamic_route: AppHarness, driver):

+ 2 - 6
reflex/.templates/jinja/web/pages/index.js.jinja2

@@ -8,6 +8,7 @@
 
 
 {% block export %}
 {% block export %}
 export default function Component() {
 export default function Component() {
+  const {{state_name}} = useContext(StateContext)
   const {{const.router}} = useRouter()
   const {{const.router}} = useRouter()
   const { {{const.color_mode}}, {{const.toggle_color_mode}} } = {{const.use_color_mode}}()
   const { {{const.color_mode}}, {{const.toggle_color_mode}} } = {{const.use_color_mode}}()
   const focusRef = useRef();
   const focusRef = useRef();
@@ -19,10 +20,7 @@ export default function Component() {
   }))
   }))
 
 
   // Main event loop.
   // Main event loop.
-  const [{{state_name}}, Event, notConnected] = useEventLoop(
-    {{initial_state|json_dumps}},
-    [E('{{state_name}}.{{const.hydrate}}', {})],
-  )
+  const [Event, notConnected] = useContext(EventLoopContext)
 
 
   // Set focus to the specified element.
   // Set focus to the specified element.
   useEffect(() => {
   useEffect(() => {
@@ -31,7 +29,6 @@ export default function Component() {
     }
     }
   })
   })
 
 
-  {% if is_dynamic %}
   // Route after the initial page hydration.
   // Route after the initial page hydration.
   useEffect(() => {
   useEffect(() => {
     const change_complete = () => Event([E('{{state_name}}.{{const.hydrate}}', {})])
     const change_complete = () => Event([E('{{state_name}}.{{const.hydrate}}', {})])
@@ -40,7 +37,6 @@ export default function Component() {
       {{const.router}}.events.off('routeChangeComplete', change_complete)
       {{const.router}}.events.off('routeChangeComplete', change_complete)
     }
     }
   }, [{{const.router}}])
   }, [{{const.router}}])
-  {% endif %}
 
 
   {% for hook in hooks %}
   {% for hook in hooks %}
   {{ hook }}
   {{ hook }}

+ 7 - 0
reflex/.templates/jinja/web/utils/context.js.jinja2

@@ -0,0 +1,7 @@
+import { createContext } from "react"
+import { E } from "/utils/state.js"
+
+export const initialState = {{ initial_state|json_dumps }}
+export const initialEvents = [E('{{state_name}}.{{const.hydrate}}', {})]
+export const StateContext = createContext(null);
+export const EventLoopContext = createContext(null);

+ 19 - 1
reflex/.templates/web/pages/_app.js

@@ -1,6 +1,8 @@
 import { ChakraProvider, extendTheme } from "@chakra-ui/react";
 import { ChakraProvider, extendTheme } from "@chakra-ui/react";
 import { Global, css } from "@emotion/react";
 import { Global, css } from "@emotion/react";
 import theme from "/utils/theme";
 import theme from "/utils/theme";
+import { initialEvents, initialState, StateContext, EventLoopContext } from "/utils/context.js";
+import { useEventLoop } from "utils/state";
 
 
 import '../styles/tailwind.css'
 import '../styles/tailwind.css'
 
 
@@ -12,11 +14,27 @@ const GlobalStyles = css`
   }
   }
 `;
 `;
 
 
+function EventLoopProvider({ children }) {
+  const [state, Event, notConnected] = useEventLoop(
+    initialState,
+    initialEvents,
+  )
+  return (
+    <EventLoopContext.Provider value={[Event, notConnected]}>
+      <StateContext.Provider value={state}>
+        {children}
+      </StateContext.Provider>
+    </EventLoopContext.Provider>
+  )
+}
+
 function MyApp({ Component, pageProps }) {
 function MyApp({ Component, pageProps }) {
   return (
   return (
     <ChakraProvider theme={extendTheme(theme)}>
     <ChakraProvider theme={extendTheme(theme)}>
       <Global styles={GlobalStyles} />
       <Global styles={GlobalStyles} />
-      <Component {...pageProps} />
+      <EventLoopProvider>
+        <Component {...pageProps} />
+      </EventLoopProvider>
     </ChakraProvider>
     </ChakraProvider>
   );
   );
 }
 }

+ 3 - 0
reflex/app.py

@@ -517,6 +517,9 @@ class App(Base):
         # Compile the theme.
         # Compile the theme.
         compile_results.append(compiler.compile_theme(self.style))
         compile_results.append(compiler.compile_theme(self.style))
 
 
+        # Compile the contexts.
+        compile_results.append(compiler.compile_contexts(self.state))
+
         # Compile the Tailwind config.
         # Compile the Tailwind config.
         if config.tailwind is not None:
         if config.tailwind is not None:
             config.tailwind["content"] = config.tailwind.get(
             config.tailwind["content"] = config.tailwind.get(

+ 40 - 6
reflex/compiler/compiler.py

@@ -6,7 +6,6 @@ from typing import List, Set, Tuple, Type
 from reflex import constants
 from reflex import constants
 from reflex.compiler import templates, utils
 from reflex.compiler import templates, utils
 from reflex.components.component import Component, ComponentStyle, CustomComponent
 from reflex.components.component import Component, ComponentStyle, CustomComponent
-from reflex.route import get_route_args
 from reflex.state import State
 from reflex.state import State
 from reflex.utils import imports
 from reflex.utils import imports
 from reflex.vars import ImportVar
 from reflex.vars import ImportVar
@@ -18,6 +17,7 @@ DEFAULT_IMPORTS: imports.ImportDict = {
         ImportVar(tag="useEffect"),
         ImportVar(tag="useEffect"),
         ImportVar(tag="useRef"),
         ImportVar(tag="useRef"),
         ImportVar(tag="useState"),
         ImportVar(tag="useState"),
+        ImportVar(tag="useContext"),
     },
     },
     "next/router": {ImportVar(tag="useRouter")},
     "next/router": {ImportVar(tag="useRouter")},
     f"/{constants.STATE_PATH}": {
     f"/{constants.STATE_PATH}": {
@@ -31,6 +31,10 @@ DEFAULT_IMPORTS: imports.ImportDict = {
         ImportVar(tag="getAllLocalStorageItems"),
         ImportVar(tag="getAllLocalStorageItems"),
         ImportVar(tag="useEventLoop"),
         ImportVar(tag="useEventLoop"),
     },
     },
+    "/utils/context.js": {
+        ImportVar(tag="EventLoopContext"),
+        ImportVar(tag="StateContext"),
+    },
     "": {ImportVar(tag="focus-visible/dist/focus-visible")},
     "": {ImportVar(tag="focus-visible/dist/focus-visible")},
     "@chakra-ui/react": {
     "@chakra-ui/react": {
         ImportVar(tag=constants.USE_COLOR_MODE),
         ImportVar(tag=constants.USE_COLOR_MODE),
@@ -67,11 +71,25 @@ def _compile_theme(theme: dict) -> str:
     return templates.THEME.render(theme=theme)
     return templates.THEME.render(theme=theme)
 
 
 
 
+def _compile_contexts(state: Type[State]) -> str:
+    """Compile the initial state and contexts.
+
+    Args:
+        state: The app state.
+
+    Returns:
+        The compiled context file.
+    """
+    return templates.CONTEXT.render(
+        initial_state=utils.compile_state(state),
+        state_name=state.get_name(),
+    )
+
+
 def _compile_page(
 def _compile_page(
     component: Component,
     component: Component,
     state: Type[State],
     state: Type[State],
     connect_error_component,
     connect_error_component,
-    is_dynamic: bool,
 ) -> str:
 ) -> str:
     """Compile the component given the app state.
     """Compile the component given the app state.
 
 
@@ -79,7 +97,6 @@ def _compile_page(
         component: The component to compile.
         component: The component to compile.
         state: The app state.
         state: The app state.
         connect_error_component: The component to render on sever connection error.
         connect_error_component: The component to render on sever connection error.
-        is_dynamic: if True, include route change re-hydration logic
 
 
     Returns:
     Returns:
         The compiled component.
         The compiled component.
@@ -93,13 +110,11 @@ def _compile_page(
     return templates.PAGE.render(
     return templates.PAGE.render(
         imports=imports,
         imports=imports,
         custom_codes=component.get_custom_code(),
         custom_codes=component.get_custom_code(),
-        initial_state=utils.compile_state(state),
         state_name=state.get_name(),
         state_name=state.get_name(),
         hooks=component.get_hooks(),
         hooks=component.get_hooks(),
         render=component.render(),
         render=component.render(),
         transports=constants.Transports.POLLING_WEBSOCKET.get_transports(),
         transports=constants.Transports.POLLING_WEBSOCKET.get_transports(),
         err_comp=connect_error_component.render() if connect_error_component else None,
         err_comp=connect_error_component.render() if connect_error_component else None,
-        is_dynamic=is_dynamic,
     )
     )
 
 
 
 
@@ -186,6 +201,23 @@ def compile_theme(style: ComponentStyle) -> Tuple[str, str]:
     return output_path, code
     return output_path, code
 
 
 
 
+def compile_contexts(
+    state: Type[State],
+) -> Tuple[str, str]:
+    """Compile the initial state / context.
+
+    Args:
+        state: The app state.
+
+    Returns:
+        The path and code of the compiled context.
+    """
+    # Get the path for the output file.
+    output_path = utils.get_context_path()
+
+    return output_path, _compile_contexts(state)
+
+
 def compile_page(
 def compile_page(
     path: str,
     path: str,
     component: Component,
     component: Component,
@@ -208,7 +240,9 @@ def compile_page(
 
 
     # Add the style to the component.
     # Add the style to the component.
     code = _compile_page(
     code = _compile_page(
-        component, state, connect_error_component, is_dynamic=bool(get_route_args(path))
+        component,
+        state,
+        connect_error_component,
     )
     )
     return output_path, code
     return output_path, code
 
 

+ 3 - 0
reflex/compiler/templates.py

@@ -65,6 +65,9 @@ DOCUMENT_ROOT = get_template("web/pages/_document.js.jinja2")
 # Template for the theme file.
 # Template for the theme file.
 THEME = get_template("web/utils/theme.js.jinja2")
 THEME = get_template("web/utils/theme.js.jinja2")
 
 
+# Template for the context file.
+CONTEXT = get_template("web/utils/context.js.jinja2")
+
 # Template for Tailwind config.
 # Template for Tailwind config.
 TAILWIND_CONFIG = get_template("web/tailwind.config.js.jinja2")
 TAILWIND_CONFIG = get_template("web/tailwind.config.js.jinja2")
 
 

+ 9 - 0
reflex/compiler/utils.py

@@ -236,6 +236,15 @@ def get_theme_path() -> str:
     return os.path.join(constants.WEB_UTILS_DIR, constants.THEME + constants.JS_EXT)
     return os.path.join(constants.WEB_UTILS_DIR, constants.THEME + constants.JS_EXT)
 
 
 
 
+def get_context_path() -> str:
+    """Get the path of the context / initial state file.
+
+    Returns:
+        The path of the context module.
+    """
+    return os.path.join(constants.WEB_UTILS_DIR, "context" + constants.JS_EXT)
+
+
 def get_components_path() -> str:
 def get_components_path() -> str:
     """Get the path of the compiled components.
     """Get the path of the compiled components.