Move initial state to separate file (#1599)

This commit is contained in:
Masen Furer 2023-08-17 09:54:07 -07:00 committed by GitHub
parent 26e45c1f18
commit afcbe7e5a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 84 additions and 17 deletions

View File

@ -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):

View File

@ -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( const [Event, notConnected] = useContext(EventLoopContext)
{{initial_state|json_dumps}},
[E('{{state_name}}.{{const.hydrate}}', {})],
)
// 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 }}

View File

@ -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);

View File

@ -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} />
<EventLoopProvider>
<Component {...pageProps} /> <Component {...pageProps} />
</EventLoopProvider>
</ChakraProvider> </ChakraProvider>
); );
} }

View File

@ -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(

View File

@ -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

View File

@ -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")

View File

@ -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.