Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
14d8d30
Add StateToken[TOKEN_TYPE] for flexible state manager
masenf Mar 8, 2026
c4b191f
Use StateToken with redis and memory managers
masenf Mar 8, 2026
cdacd22
disambiguate class name
masenf Mar 8, 2026
d463ef8
handle legacy tokens passed to app.modify_state
masenf Mar 17, 2026
b715c27
Add EventContext module
masenf Mar 17, 2026
57d0d01
Move BaseState event processing to reflex.ievent.processor package
masenf Mar 20, 2026
b8036bd
Overhaul EventProcessor lifecycle
masenf Mar 24, 2026
01befb5
Fix test_app to use BaseStateEventProcessor
masenf Mar 25, 2026
cfc706a
Avoid handling the same exception multiple times in EventProcessor
masenf Mar 25, 2026
8bbeab4
Attach cls to setter event handlers
masenf Mar 25, 2026
2562606
fix test_app, test_state and friends
masenf Mar 25, 2026
ea93b5b
ENG-9198: implement ContextVar-based registry for BaseState and Event…
masenf Mar 26, 2026
1cfd974
Remove `token` field from Event
masenf Mar 26, 2026
6be4f72
Clean up frontend Event and StateUpdate
masenf Mar 26, 2026
1e86d61
remove get_app dependency from get_state and background tasks
masenf Mar 26, 2026
a1bc4b5
Remove remaining get_app / mock_app dependency from tests
masenf Mar 26, 2026
fcb24f6
Merge remote-tracking branch 'origin/main' into masenf/event-context
masenf Mar 30, 2026
9256212
EventContext inherits from BaseContext
masenf Mar 26, 2026
0c04bc3
additional fixups
masenf Mar 30, 2026
5940c8d
apply changes to migrated files separately
masenf Mar 30, 2026
b13ad37
add missing import
masenf Mar 30, 2026
ada96c8
remove pyleak integration from base_state_processor
masenf Mar 30, 2026
a1eb180
EventProcessor.enqueue_stream_delta and task Future
masenf Mar 30, 2026
a073b0d
Adapt upload endpoint to new EventProcessor
masenf Mar 30, 2026
700dc74
Fix test_expiration.py and other new state tests
masenf Mar 30, 2026
aa15baa
Fix upload tests for new EventProcessor fixtures
masenf Mar 30, 2026
b709bee
add OPLOCK_ENABLED state_manager.close to tests
masenf Mar 31, 2026
9e706cc
state.js: pass around params as a ref
masenf Mar 31, 2026
53815fd
Merge remote-tracking branch 'origin/main' into masenf/event-context-rb
masenf Mar 31, 2026
fc311d3
registry: substate tracking and stateful component cache
masenf Apr 1, 2026
d9a996d
close old locks in disk/memory state manager
masenf Apr 1, 2026
55e0449
Remove state_manager from AppHarness
masenf Apr 1, 2026
d914ea0
Move reflex._internal to reflex_core._internal
masenf Apr 1, 2026
c50d2b2
move reflex.ievent to reflex_core._internal.event
masenf Apr 1, 2026
9650b0f
replace "reload" functionality with internal rehydration
masenf Apr 1, 2026
465e6d0
incldue coverage from subpackages
masenf Apr 1, 2026
06270b3
remove simulated pre-hydrated states
masenf Apr 1, 2026
5ed431f
Add unit test cases for new registry/context/processor modules
masenf Apr 1, 2026
3980c49
Merge remote-tracking branch 'origin/main' into masenf/event-context-rb
masenf Apr 1, 2026
7bdc7df
Use correct token in enqueue_stream_delta
masenf Apr 1, 2026
c046613
Fix StateToken.deserialize implementation
masenf Apr 1, 2026
945662b
Update packages/reflex-core/src/reflex_core/_internal/event/processor…
masenf Apr 1, 2026
28b105f
Fix StateToken mismerge (Thanks greptile)
masenf Apr 1, 2026
5021453
move EventChain import to avoid circular dep
masenf Apr 1, 2026
d54e178
fix StateToken deserialize tests
masenf Apr 1, 2026
aece85b
Python 3.11 and 3.12 compatibility
masenf Apr 1, 2026
458d99e
Merge remote-tracking branch 'origin/main' into masenf/event-context-rb
masenf Apr 1, 2026
0fff344
py3.10 Self compat
masenf Apr 1, 2026
8729b59
py3.10: typing_extensions Self
masenf Apr 1, 2026
b1ad918
ugh more py3.10 Self compat
masenf Apr 1, 2026
ed82a6c
fix reflex_core -> reflex import
masenf Apr 1, 2026
0f05b66
Handle py3.11 compatible queue shutdown better
masenf Apr 1, 2026
75e160a
state.js: pump the queue in processEvent
masenf Apr 1, 2026
f60c4bd
AppHarness: pre-register SharedState so it's available in the base Re…
masenf Apr 1, 2026
43f97ab
BaseStateEventProcessor: emit deltas before enqueuing events
masenf Apr 1, 2026
8987a7e
set RegistrationContext in ASGI middleware
masenf Apr 1, 2026
5d609e4
EventFuture: tracks execution of chained events
masenf Apr 2, 2026
be1a18d
Add BaseState to reflex_core.event namespace for docgen
masenf Apr 2, 2026
7f3271e
EventProcessor.enqueue only accepts a single Event
masenf Apr 2, 2026
b3c8178
attach the registration_context_middleware in App.__call__
masenf Apr 2, 2026
83ba6ee
test_connection_banner: use CDP to simulate network offline
masenf Apr 2, 2026
3d60731
Merge remote-tracking branch 'origin/main' into masenf/event-context-rb
masenf Apr 2, 2026
5b36147
reflex_core.event: provide BaseState as a namespace property
masenf Apr 2, 2026
26160e4
Track EventFuture children
masenf Apr 2, 2026
6fc9d6a
use py3.11 compatible super() for dataclasses with slots
masenf Apr 3, 2026
1a04463
Only process one non-backend event per token
masenf Apr 3, 2026
73e2bbe
Fix event order assertions in test_event_chain
masenf Apr 3, 2026
5b056ad
py3.11 super() fix again
masenf Apr 3, 2026
fe0bc5b
Move _registration_context_middleware to top of asgi stack
masenf Apr 3, 2026
f6dc002
Move registration context middle to not quite the top level app.
masenf Apr 3, 2026
3a0f532
Add cache_key and lock_key attributes to StateToken
masenf Apr 3, 2026
46232ac
Use cache_key and lock_key in StateManagerDisk and StateManagerRedis
masenf Apr 3, 2026
ca9c2b0
Merge remote-tracking branch 'origin/main' into masenf/event-context-rb
masenf Apr 4, 2026
b7afcef
update pyi_hashes
masenf Apr 6, 2026
db87434
make reflex_base.event a package
masenf Apr 6, 2026
0fa3f7e
Get rid of reflex_base._internal namespace
masenf Apr 6, 2026
e2c1ed1
test_upload: extend sleep before cancellation
masenf Apr 6, 2026
ca3ac5a
Merge remote-tracking branch 'origin/main' into masenf/event-context-rb
masenf Apr 6, 2026
4ae61e2
re-add fix_events token param
masenf Apr 6, 2026
80542a3
Add StateManager.state property as a compat shim
masenf Apr 6, 2026
e8644ef
deprecate StateUpdate.final (instead of removal)
masenf Apr 6, 2026
27f09a6
Support legacy token format in StateManager implementations
masenf Apr 6, 2026
b4a8dad
add test case for StateManager legacy str tokens
masenf Apr 6, 2026
0d5ed85
ignore QueueShutDown when shutting down queue
masenf Apr 6, 2026
78d7040
optimize test_event_processing benchmark
masenf Apr 6, 2026
a34eab4
asyncio.QueueShutDown was only added in 3.13+
masenf Apr 6, 2026
98241e0
Add deprecated typing hints for passing str token to StateManager
masenf Apr 6, 2026
62deadf
Move overloads outside of if TYPE_CHECKING
masenf Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 59 additions & 86 deletions packages/reflex-base/src/reflex_base/.templates/web/utils/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ const cookies = new Cookies();
// Dictionary holding component references.
export const refs = {};

// Flag ensures that only one event is processing on the backend concurrently.
let event_processing = false;
// Array holding pending events to be processed.
const event_queue = [];

Expand Down Expand Up @@ -203,30 +201,28 @@ function urlFrom(string) {
* @param socket The socket object to send the event on.
* @param navigate The navigate function from useNavigate
* @param params The params object from useParams
*
* @returns True if the event was sent, false if it was handled locally.
*/
export const applyEvent = async (event, socket, navigate, params) => {
// Handle special events
if (event.name == "_redirect") {
if ((event.payload.path ?? undefined) === undefined) {
return false;
return;
}
if (event.payload.external) {
window.open(
event.payload.path,
"_blank",
"noopener" + (event.payload.popup ? ",popup" : ""),
);
return false;
return;
}
const url = urlFrom(event.payload.path);
let pathname = event.payload.path;
if (url) {
if (url.host !== window.location.host) {
// External URL
window.location.assign(event.payload.path);
return false;
return;
} else {
pathname = url.pathname + url.search + url.hash;
}
Expand All @@ -236,37 +232,37 @@ export const applyEvent = async (event, socket, navigate, params) => {
} else {
navigate(pathname);
}
return false;
return;
}

if (event.name == "_remove_cookie") {
cookies.remove(event.payload.key, { ...event.payload.options });
queueEventIfSocketExists(initialEvents(), socket, navigate, params);
return false;
return;
}

if (event.name == "_clear_local_storage") {
localStorage.clear();
queueEventIfSocketExists(initialEvents(), socket, navigate, params);
return false;
return;
}

if (event.name == "_remove_local_storage") {
localStorage.removeItem(event.payload.key);
queueEventIfSocketExists(initialEvents(), socket, navigate, params);
return false;
return;
}

if (event.name == "_clear_session_storage") {
sessionStorage.clear();
queueEventIfSocketExists(initialEvents(), socket, navigate, params);
return false;
return;
}

if (event.name == "_remove_session_storage") {
sessionStorage.removeItem(event.payload.key);
queueEventIfSocketExists(initialEvents(), socket, navigate, params);
return false;
return;
}

if (event.name == "_download") {
Expand All @@ -285,7 +281,7 @@ export const applyEvent = async (event, socket, navigate, params) => {
a.download = event.payload.filename;
a.click();
a.remove();
return false;
return;
}

if (event.name == "_set_focus") {
Expand All @@ -299,7 +295,7 @@ export const applyEvent = async (event, socket, navigate, params) => {
} else {
current.focus();
}
return false;
return;
}

if (event.name == "_blur_focus") {
Expand All @@ -313,7 +309,7 @@ export const applyEvent = async (event, socket, navigate, params) => {
} else {
current.blur();
}
return false;
return;
}

if (event.name == "_set_value") {
Expand All @@ -322,7 +318,7 @@ export const applyEvent = async (event, socket, navigate, params) => {
if (ref.current) {
ref.current.value = event.payload.value;
}
return false;
return;
}

if (
Expand All @@ -348,7 +344,7 @@ export const applyEvent = async (event, socket, navigate, params) => {
window.onerror(e.message, null, null, null, e);
}
}
return false;
return;
}

if (event.name == "_call_script" || event.name == "_call_function") {
Expand All @@ -375,36 +371,35 @@ export const applyEvent = async (event, socket, navigate, params) => {
window.onerror(e.message, null, null, null, e);
}
}
return false;
return;
}

// Update token and router data (if missing).
event.token = getToken();
if (
event.router_data === undefined ||
Object.keys(event.router_data).length === 0
) {
// Since we don't have router directly, we need to get info from our hooks
event.router_data = {
pathname: window.location.pathname,
query: {
...Object.fromEntries(new URLSearchParams(window.location.search)),
...params(),
},
asPath:
window.location.pathname +
window.location.search +
window.location.hash,
};
const query = {
...Object.fromEntries(new URLSearchParams(window.location.search)),
...params.current,
};
if (query && Object.keys(query).length > 0) {
event.router_data.query = query;
}
}

// Send the event to the server.
if (socket) {
socket.emit("event", event);
return true;
}

return false;
};

/**
Expand All @@ -413,11 +408,8 @@ export const applyEvent = async (event, socket, navigate, params) => {
* @param socket The socket object to send the response event(s) on.
* @param navigate The navigate function from React Router
* @param params The params object from React Router
*
* @returns Whether the event was sent.
*/
export const applyRestEvent = async (event, socket, navigate, params) => {
let eventSent = false;
if (event.handler === "uploadFiles") {
// Start upload, but do not wait for it, which would block other events.
uploadFiles(
Expand All @@ -431,9 +423,7 @@ export const applyRestEvent = async (event, socket, navigate, params) => {
getBackendURL,
getToken,
);
return false;
}
return eventSent;
};

/**
Expand Down Expand Up @@ -487,28 +477,21 @@ export const processEvent = async (socket, navigate, params) => {
}

// Only proceed if we're not already processing an event.
if (event_queue.length === 0 || event_processing) {
if (event_queue.length === 0) {
return;
}

// Set processing to true to block other events from being processed.
event_processing = true;

// Apply the next event in the queue.
const event = event_queue.shift();

let eventSent = false;
// Process events with handlers via REST and all others via websockets.
if (event.handler) {
eventSent = await applyRestEvent(event, socket, navigate, params);
await applyRestEvent(event, socket, navigate, params);
} else {
eventSent = await applyEvent(event, socket, navigate, params);
await applyEvent(event, socket, navigate, params);
}
// If no event was sent, set processing to false.
if (!eventSent) {
event_processing = false;
// recursively call processEvent to drain the queue, since there is
// no state update to trigger the useEffect event loop.
// Process any remaining events.
if (event_queue.length > 0) {
await processEvent(socket, navigate, params);
}
};
Expand Down Expand Up @@ -621,17 +604,11 @@ export const connect = async (
window.addEventListener("unload", disconnectTrigger);
if (socket.current.rehydrate) {
socket.current.rehydrate = false;
queueEvents(
initialEvents(),
socket,
true,
navigate,
() => params.current,
);
queueEvents(initialEvents(), socket, true, navigate, params);
}
// Drain any initial events from the queue.
while (event_queue.length > 0 && !event_processing) {
await processEvent(socket.current, navigate, () => params.current);
while (event_queue.length > 0) {
await processEvent(socket.current, navigate, params);
}
});

Expand All @@ -650,12 +627,10 @@ export const connect = async (
}, 200 * n_connect_errors); // Incremental backoff
});

// When the socket disconnects reset the event_processing flag
socket.current.on("disconnect", (reason, details) => {
socket.current.wait_connect = false;
const try_reconnect =
reason !== "io server disconnect" && reason !== "io client disconnect";
event_processing = false;
window.removeEventListener("unload", disconnectTrigger);
window.removeEventListener("beforeunload", disconnectTrigger);
window.removeEventListener("pagehide", pagehideHandler);
Expand All @@ -667,30 +642,24 @@ export const connect = async (

// On each received message, queue the updates and events.
socket.current.on("event", async (update) => {
for (const substate in update.delta) {
dispatch[substate](update.delta[substate]);
// handle events waiting for `is_hydrated`
if (
substate === state_name &&
update.delta[substate]?.is_hydrated_rx_state_
) {
queueEvents(on_hydrated_queue, socket, false, navigate, params);
on_hydrated_queue.length = 0;
if (update.delta && Object.keys(update.delta).length > 0) {
for (const substate in update.delta) {
dispatch[substate](update.delta[substate]);
// handle events waiting for `is_hydrated`
if (
substate === state_name &&
update.delta[substate]?.is_hydrated_rx_state_
) {
queueEvents(on_hydrated_queue, socket, false, navigate, params);
on_hydrated_queue.length = 0;
}
}
applyClientStorageDelta(client_storage, update.delta);
}
applyClientStorageDelta(client_storage, update.delta);
if (update.final !== null) {
event_processing = !update.final;
}
if (update.events) {
if (update.events && update.events.length > 0) {
queueEvents(update.events, socket, false, navigate, params);
}
});
socket.current.on("reload", async (event) => {
event_processing = false;
on_hydrated_queue.push(event);
queueEvents(initialEvents(), socket, true, navigate, params);
});
socket.current.on("new_token", async (new_token) => {
token = new_token;
window.sessionStorage.setItem(TOKEN_KEY, new_token);
Expand All @@ -713,7 +682,17 @@ export const ReflexEvent = (
event_actions = {},
handler = null,
) => {
return { name, payload, handler, event_actions };
const e = { name };
if (payload && Object.keys(payload).length > 0) {
e.payload = payload;
}
if (event_actions && Object.keys(event_actions).length > 0) {
e.event_actions = event_actions;
}
if (handler !== null) {
e.handler = handler;
}
return e;
};

/**
Expand Down Expand Up @@ -919,7 +898,7 @@ export const useEventLoop = (
setConnectErrors,
client_storage,
navigate,
() => params.current,
params,
);
}
}, [
Expand Down Expand Up @@ -947,7 +926,7 @@ export const useEventLoop = (
}

return applyEventActions(
() => queueEvents(_events, socket, false, navigate, () => params.current),
() => queueEvents(_events, socket, false, navigate, params),
event_actions,
args,
_events.map((e) => e.name).join("+++"),
Expand All @@ -958,13 +937,7 @@ export const useEventLoop = (
const sentHydrate = useRef(false); // Avoid double-hydrate due to React strict-mode
useEffect(() => {
if (!sentHydrate.current) {
queueEvents(
initial_events(),
socket,
true,
navigate,
() => params.current,
);
queueEvents(initial_events(), socket, true, navigate, params);
sentHydrate.current = true;
}
}, []);
Expand Down Expand Up @@ -1028,9 +1001,9 @@ export const useEventLoop = (
}
(async () => {
// Process all outstanding events.
while (event_queue.length > 0 && !event_processing) {
while (event_queue.length > 0) {
await ensureSocketConnected();
await processEvent(socket.current, navigate, () => params.current);
await processEvent(socket.current, navigate, params);
}
})();
});
Expand Down
Loading
Loading