Skip to content

Commit 86b32f4

Browse files
committed
fix context template
1 parent 3ca941e commit 86b32f4

3 files changed

Lines changed: 158 additions & 37 deletions

File tree

reflex/compiler/compiler.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,17 @@ def _compile_contexts(state: type[BaseState] | None, theme: Component | None) ->
130130
appearance = LiteralVar.create(SYSTEM_COLOR_MODE)
131131

132132
return (
133-
templates.CONTEXT.render(
133+
templates.context_template(
134134
initial_state=utils.compile_state(state),
135135
state_name=state.get_name(),
136136
client_storage=utils.compile_client_storage(state),
137137
is_dev_mode=not is_prod_mode(),
138-
default_color_mode=appearance,
138+
default_color_mode=str(appearance),
139139
)
140140
if state
141-
else templates.CONTEXT.render(
141+
else templates.context_template(
142142
is_dev_mode=not is_prod_mode(),
143-
default_color_mode=appearance,
143+
default_color_mode=str(appearance),
144144
)
145145
)
146146

reflex/compiler/templates.py

Lines changed: 142 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -351,38 +351,161 @@ def theme_template(theme: str):
351351
return f"""export default {theme}"""
352352

353353

354-
def _context_template(**kwargs):
354+
def context_template(
355+
*,
356+
is_dev_mode: bool,
357+
default_color_mode: str,
358+
initial_state: dict[str, Any] | None = None,
359+
state_name: str | None = None,
360+
client_storage: dict[str, dict[str, dict[str, Any]]] | None = None,
361+
):
355362
"""Template for the context file.
356363
357364
Args:
358-
**kwargs: Template context variables including initial_state, state_name, etc.
365+
initial_state: The initial state for the context.
366+
state_name: The name of the state.
367+
client_storage: The client storage for the context.
368+
is_dev_mode: Whether the app is in development mode.
369+
default_color_mode: The default color mode for the context.
359370
360371
Returns:
361372
Rendered context file content as string.
362373
"""
363-
initial_state = kwargs.get("initial_state", "null")
364-
state_name = kwargs.get("state_name", "")
365-
client_storage = kwargs.get("client_storage", "")
366-
is_dev_mode = kwargs.get("is_dev_mode", False)
367-
default_color_mode = kwargs.get("default_color_mode", "")
374+
initial_state = initial_state or {}
375+
state_contexts_str = "".join(
376+
[f"{state_name}: createContext(null)," for state_name in initial_state]
377+
)
378+
379+
state_str = (
380+
rf"""
381+
export const state_name = "{state_name}"
382+
383+
export const exception_state_name = "{constants.CompileVars.FRONTEND_EXCEPTION_STATE_FULL}"
384+
385+
// These events are triggered on initial load and each page navigation.
386+
export const onLoadInternalEvent = () => {{
387+
const internal_events = [];
388+
389+
// Get tracked cookie and local storage vars to send to the backend.
390+
const client_storage_vars = hydrateClientStorage(clientStorage);
391+
// But only send the vars if any are actually set in the browser.
392+
if (client_storage_vars && Object.keys(client_storage_vars).length !== 0) {{
393+
internal_events.push(
394+
Event(
395+
'{state_name}.{constants.CompileVars.UPDATE_VARS_INTERNAL}',
396+
{{vars: client_storage_vars}},
397+
),
398+
);
399+
}}
400+
401+
// `on_load_internal` triggers the correct on_load event(s) for the current page.
402+
// If the page does not define any on_load event, this will just set `is_hydrated = true`.
403+
internal_events.push(Event('{state_name}.{constants.CompileVars.ON_LOAD_INTERNAL}'));
404+
405+
return internal_events;
406+
}}
407+
408+
// The following events are sent when the websocket connects or reconnects.
409+
export const initialEvents = () => [
410+
Event('{state_name}.{constants.CompileVars.HYDRATE}'),
411+
...onLoadInternalEvent()
412+
]
413+
"""
414+
if state_name
415+
else """
416+
export const state_name = undefined
368417
369-
return f"""import {{ createContext, useContext, useMemo, useReducer, useState }} from "react"
418+
export const exception_state_name = undefined
370419
371-
export const StateContexts = {{
372-
{state_name or ""}: createContext(null),
420+
export const onLoadInternalEvent = () => []
421+
422+
export const initialEvents = () => []
423+
"""
424+
)
425+
426+
state_reducer_str = "\n".join(
427+
rf'const [{state_name}, dispatch_{state_name}] = useReducer(applyDelta, initialState["{state_name}"])'
428+
for state_name in initial_state
429+
)
430+
431+
create_state_contexts_str = "\n".join(
432+
rf"createElement(StateContexts.{state_name},{{value: {state_name}}},"
433+
for state_name in initial_state
434+
)
435+
436+
dispatchers_str = "\n".join(
437+
f'"{state_name}": dispatch_{state_name},' for state_name in initial_state
438+
)
439+
440+
return rf"""import {{ createContext, useContext, useMemo, useReducer, useState, createElement, useEffect }} from "react"
441+
import {{ applyDelta, Event, hydrateClientStorage, useEventLoop, refs }} from "$/utils/state"
442+
import {{ jsx }} from "@emotion/react";
443+
444+
export const initialState = {"{}" if initial_state else json_dumps(initial_state)}
445+
446+
export const defaultColorMode = { default_color_mode }
447+
export const ColorModeContext = createContext(null);
448+
export const UploadFilesContext = createContext(null);
449+
export const DispatchContext = createContext(null);
450+
export const StateContexts = {{{state_contexts_str}}};
451+
export const EventLoopContext = createContext(null);
452+
export const clientStorage = {"{}" if client_storage is None else json_dumps(client_storage)}
453+
454+
{state_str}
455+
456+
export const isDevMode = {json.dumps(is_dev_mode)};
457+
458+
export function UploadFilesProvider({{ children }}) {{
459+
const [filesById, setFilesById] = useState({{}})
460+
refs["__clear_selected_files"] = (id) => setFilesById(filesById => {{
461+
const newFilesById = {{...filesById}}
462+
delete newFilesById[id]
463+
return newFilesById
464+
}})
465+
return createElement(
466+
UploadFilesContext.Provider,
467+
{{ value: [filesById, setFilesById] }},
468+
children
469+
);
373470
}}
374471
375-
export const EventContexts = {{
376-
{state_name or ""}: createContext(null),
472+
export function ClientSide(component) {{
473+
return ({{ children, ...props }}) => {{
474+
const [Component, setComponent] = useState(null);
475+
useEffect(() => {{
476+
setComponent(component);
477+
}}, []);
478+
return Component ? jsx(Component, props, children) : null;
479+
}};
377480
}}
378481
379-
export const initialState = {initial_state}
380-
export const clientStorage = {client_storage}
381-
export const isDevMode = {str(is_dev_mode).lower()}
382-
export const colorModeManager = {{
383-
get: () => {default_color_mode},
384-
set: () => {{}},
385-
type: "localStorage"
482+
export function EventLoopProvider({{ children }}) {{
483+
const dispatch = useContext(DispatchContext)
484+
const [addEvents, connectErrors] = useEventLoop(
485+
dispatch,
486+
initialEvents,
487+
clientStorage,
488+
)
489+
return createElement(
490+
EventLoopContext.Provider,
491+
{{ value: [addEvents, connectErrors] }},
492+
children
493+
);
494+
}}
495+
496+
export function StateProvider({{ children }}) {{
497+
{state_reducer_str}
498+
const dispatchers = useMemo(() => {{
499+
return {{
500+
{dispatchers_str}
501+
}}
502+
}}, [])
503+
504+
return (
505+
{create_state_contexts_str}
506+
createElement(DispatchContext, {{value: dispatchers}}, children)
507+
{")" * len(initial_state)}
508+
)
386509
}}"""
387510

388511

@@ -698,9 +821,6 @@ def render(self, **kwargs) -> str:
698821
return self.func(**kwargs)
699822

700823

701-
# Template for the context file.
702-
CONTEXT = TemplateFunction(_context_template)
703-
704824
# Template to render a component tag.
705825
COMPONENT = TemplateFunction(_component_template)
706826

reflex/compiler/utils.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -245,30 +245,29 @@ def _compile_client_storage_field(
245245

246246
def _compile_client_storage_recursive(
247247
state: type[BaseState],
248-
) -> tuple[dict[str, dict], dict[str, dict], dict[str, dict]]:
248+
) -> tuple[
249+
dict[str, dict[str, Any]], dict[str, dict[str, Any]], dict[str, dict[str, Any]]
250+
]:
249251
"""Compile the client-side storage for the given state recursively.
250252
251253
Args:
252254
state: The app state object.
253255
254256
Returns:
255-
A tuple of the compiled client-side storage info:
256-
(
257-
cookies: dict[str, dict],
258-
local_storage: dict[str, dict[str, str]]
259-
session_storage: dict[str, dict[str, str]]
260-
).
257+
A tuple of the compiled client-side storage info: (cookies, local_storage, session_storage).
261258
"""
262-
cookies = {}
263-
local_storage = {}
264-
session_storage = {}
259+
cookies: dict[str, dict[str, Any]] = {}
260+
local_storage: dict[str, dict[str, Any]] = {}
261+
session_storage: dict[str, dict[str, Any]] = {}
265262
state_name = state.get_full_name()
266263
for name, field in state.__fields__.items():
267264
if name in state.inherited_vars:
268265
# only include vars defined in this state
269266
continue
270267
state_key = f"{state_name}.{name}" + FIELD_MARKER
271268
field_type, options = _compile_client_storage_field(field)
269+
if field_type is None or options is None:
270+
continue
272271
if field_type is Cookie:
273272
cookies[state_key] = options
274273
elif field_type is LocalStorage:
@@ -289,7 +288,9 @@ def _compile_client_storage_recursive(
289288
return cookies, local_storage, session_storage
290289

291290

292-
def compile_client_storage(state: type[BaseState]) -> dict[str, dict]:
291+
def compile_client_storage(
292+
state: type[BaseState],
293+
) -> dict[str, dict[str, dict[str, Any]]]:
293294
"""Compile the client-side storage for the given state.
294295
295296
Args:

0 commit comments

Comments
 (0)