Skip to content

Commit

Permalink
disable react strict mode for event loop (#4720)
Browse files Browse the repository at this point in the history
* disable react strict mode for event loop

* oops

* pyi oui

* separate socket connection from event loop

* prettier state.js

* disable react strict mode

* didn't work sadge

* socket connect/disconnect depends on new isBackendDisabled state

* only start the event loop when the socket is set or we're not stateful

* Always drain the queue unless backend is disabled

---------

Co-authored-by: Masen Furer <[email protected]>
  • Loading branch information
adhami3310 and masenf authored Feb 1, 2025
1 parent 83e635d commit 238b03a
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 54 deletions.
14 changes: 7 additions & 7 deletions reflex/.templates/jinja/web/pages/_app.js.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ export default function MyApp({ Component, pageProps }) {
}, []);
return (
<ThemeProvider defaultTheme={ defaultColorMode } attribute="class">
<AppWrap>
<StateProvider>
<EventLoopProvider>
<Component {...pageProps} />
</EventLoopProvider>
</StateProvider>
</AppWrap>
<StateProvider>
<EventLoopProvider>
<AppWrap>
<Component {...pageProps} />
</AppWrap>
</EventLoopProvider>
</StateProvider>
</ThemeProvider>
);
}
Expand Down
69 changes: 40 additions & 29 deletions reflex/.templates/web/utils/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ export const applyEvent = async (event, socket) => {
a.href = eval?.(
event.payload.url.replace(
"getBackendURL(env.UPLOAD)",
`"${getBackendURL(env.UPLOAD)}"`
)
`"${getBackendURL(env.UPLOAD)}"`,
),
);
}
a.download = event.payload.filename;
Expand Down Expand Up @@ -341,7 +341,7 @@ export const applyRestEvent = async (event, socket) => {
event.payload.files,
event.payload.upload_id,
event.payload.on_upload_progress,
socket
socket,
);
return false;
}
Expand Down Expand Up @@ -408,7 +408,7 @@ export const connect = async (
dispatch,
transports,
setConnectErrors,
client_storage = {}
client_storage = {},
) => {
// Get backend URL object from the endpoint.
const endpoint = getBackendURL(EVENTURL);
Expand Down Expand Up @@ -499,7 +499,7 @@ export const uploadFiles = async (
files,
upload_id,
on_upload_progress,
socket
socket,
) => {
// return if there's no file to upload
if (files === undefined || files.length === 0) {
Expand Down Expand Up @@ -604,7 +604,7 @@ export const Event = (
name,
payload = {},
event_actions = {},
handler = null
handler = null,
) => {
return { name, payload, handler, event_actions };
};
Expand All @@ -631,7 +631,7 @@ export const hydrateClientStorage = (client_storage) => {
for (const state_key in client_storage.local_storage) {
const options = client_storage.local_storage[state_key];
const local_storage_value = localStorage.getItem(
options.name || state_key
options.name || state_key,
);
if (local_storage_value !== null) {
client_storage_values[state_key] = local_storage_value;
Expand All @@ -642,7 +642,7 @@ export const hydrateClientStorage = (client_storage) => {
for (const state_key in client_storage.session_storage) {
const session_options = client_storage.session_storage[state_key];
const session_storage_value = sessionStorage.getItem(
session_options.name || state_key
session_options.name || state_key,
);
if (session_storage_value != null) {
client_storage_values[state_key] = session_storage_value;
Expand All @@ -667,7 +667,7 @@ export const hydrateClientStorage = (client_storage) => {
const applyClientStorageDelta = (client_storage, delta) => {
// find the main state and check for is_hydrated
const unqualified_states = Object.keys(delta).filter(
(key) => key.split(".").length === 1
(key) => key.split(".").length === 1,
);
if (unqualified_states.length === 1) {
const main_state = delta[unqualified_states[0]];
Expand Down Expand Up @@ -701,7 +701,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
const session_options = client_storage.session_storage[state_key];
sessionStorage.setItem(
session_options.name || state_key,
delta[substate][key]
delta[substate][key],
);
}
}
Expand All @@ -721,7 +721,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
export const useEventLoop = (
dispatch,
initial_events = () => [],
client_storage = {}
client_storage = {},
) => {
const socket = useRef(null);
const router = useRouter();
Expand All @@ -735,7 +735,7 @@ export const useEventLoop = (

event_actions = events.reduce(
(acc, e) => ({ ...acc, ...e.event_actions }),
event_actions ?? {}
event_actions ?? {},
);

const _e = args.filter((o) => o?.preventDefault !== undefined)[0];
Expand Down Expand Up @@ -763,7 +763,7 @@ export const useEventLoop = (
debounce(
combined_name,
() => queueEvents(events, socket),
event_actions.debounce
event_actions.debounce,
);
} else {
queueEvents(events, socket);
Expand All @@ -782,7 +782,7 @@ export const useEventLoop = (
query,
asPath,
}))(router),
}))
})),
);
sentHydrate.current = true;
}
Expand Down Expand Up @@ -817,13 +817,9 @@ export const useEventLoop = (
};
}, []);

// Main event loop.
// Handle socket connect/disconnect.
useEffect(() => {
// Skip if the router is not ready.
if (!router.isReady) {
return;
}
// only use websockets if state is present
// only use websockets if state is present and backend is not disabled (reflex cloud).
if (Object.keys(initialState).length > 1 && !isBackendDisabled()) {
// Initialize the websocket connection.
if (!socket.current) {
Expand All @@ -832,16 +828,31 @@ export const useEventLoop = (
dispatch,
["websocket"],
setConnectErrors,
client_storage
client_storage,
);
}
(async () => {
// Process all outstanding events.
while (event_queue.length > 0 && !event_processing) {
await processEvent(socket.current);
}
})();
}

// Cleanup function.
return () => {
if (socket.current) {
socket.current.disconnect();
}
};
}, []);

// Main event loop.
useEffect(() => {
// Skip if the router is not ready.
if (!router.isReady || isBackendDisabled()) {
return;
}
(async () => {
// Process all outstanding events.
while (event_queue.length > 0 && !event_processing) {
await processEvent(socket.current);
}
})();
});

// localStorage event handling
Expand All @@ -865,7 +876,7 @@ export const useEventLoop = (
vars[storage_to_state_map[e.key]] = e.newValue;
const event = Event(
`${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
{ vars: vars }
{ vars: vars },
);
addEvents([event], e);
}
Expand Down Expand Up @@ -958,7 +969,7 @@ export const getRefValues = (refs) => {
return refs.map((ref) =>
ref.current
? ref.current.value || ref.current.getAttribute("aria-valuenow")
: null
: null,
);
};

Expand Down
10 changes: 7 additions & 3 deletions reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
from reflex.components.base.app_wrap import AppWrap
from reflex.components.base.error_boundary import ErrorBoundary
from reflex.components.base.fragment import Fragment
from reflex.components.base.strict_mode import StrictMode
from reflex.components.component import (
Component,
ComponentStyle,
Expand Down Expand Up @@ -956,6 +957,12 @@ def get_compilation_time() -> str:
# If a theme component was provided, wrap the app with it
app_wrappers[(20, "Theme")] = self.theme

# Get the env mode.
config = get_config()

if config.react_strict_mode:
app_wrappers[(200, "StrictMode")] = StrictMode.create()

should_compile = self._should_compile()

if not should_compile:
Expand Down Expand Up @@ -1001,9 +1008,6 @@ def get_compilation_time() -> str:

progress.advance(task)

# Get the env mode.
config = get_config()

# Store the compile results.
compile_results = []

Expand Down
10 changes: 10 additions & 0 deletions reflex/components/base/strict_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Module for the StrictMode component."""

from reflex.components.component import Component


class StrictMode(Component):
"""A React strict mode component to enable strict mode for its children."""

library = "react"
tag = "StrictMode"
57 changes: 57 additions & 0 deletions reflex/components/base/strict_mode.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Stub file for reflex/components/base/strict_mode.py"""

# ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
from typing import Any, Dict, Optional, Union, overload

from reflex.components.component import Component
from reflex.event import BASE_STATE, EventType
from reflex.style import Style
from reflex.vars.base import Var

class StrictMode(Component):
@overload
@classmethod
def create( # type: ignore
cls,
*children,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,
class_name: Optional[Any] = None,
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
on_blur: Optional[EventType[[], BASE_STATE]] = None,
on_click: Optional[EventType[[], BASE_STATE]] = None,
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
on_focus: Optional[EventType[[], BASE_STATE]] = None,
on_mount: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
**props,
) -> "StrictMode":
"""Create the component.
Args:
*children: The children of the component.
style: The style of the component.
key: A unique key for the component.
id: The id for the component.
class_name: The class name for the component.
autofocus: Whether the component should take the focus once the page is loaded
custom_attrs: custom attribute
**props: The props of the component.
Returns:
The component.
"""
...
1 change: 0 additions & 1 deletion reflex/utils/prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,6 @@ def _update_next_config(
next_config = {
"basePath": config.frontend_path or "",
"compress": config.next_compression,
"reactStrictMode": config.react_strict_mode,
"trailingSlash": True,
"staticPageGenerationTimeout": config.static_page_generation_timeout,
}
Expand Down
Loading

0 comments on commit 238b03a

Please sign in to comment.