Khaleel Al-Adhami пре 2 месеци
родитељ
комит
859e8be2dc

+ 20 - 14
reflex/.templates/web/utils/state.js

@@ -128,7 +128,7 @@ export const isStateful = () => {
   if (event_queue.length === 0) {
     return false;
   }
-  return event_queue.some((event) => event.name.startsWith("reflex___state"));
+  return event_queue.some((event) => event.name.startsWith(state_name));
 };
 
 /**
@@ -471,10 +471,11 @@ export const connect = async (
   const getSubstateFromUpdate = (update, substate_name) => {
     if (update.__patch) {
       if (last_substate_hash[substate_name] !== update.__previous_hash) {
-        throw new Error("Patch received out of order");
+        return null;
       }
       last_substate_hash[substate_name] = update.__hash;
-      return applyPatch(last_substate_info, update.__patch).newDocument;
+      return applyPatch(last_substate_info[substate_name], update.__patch)
+        .newDocument;
     } else {
       last_substate_hash[substate_name] = update.__hash;
       return update.__full;
@@ -483,24 +484,29 @@ export const connect = async (
 
   // On each received message, queue the updates and events.
   socket.current.on("event", async (update) => {
-    const failed_states = [];
+    const failed_substates = [];
     for (const substate in update.delta) {
-      try {
-        const new_substate_info = getSubstateFromUpdate(
-          update.delta[substate],
-          substate
-        );
-      } catch (e) {
-        console.error("Received patch out of order", e);
-        states_failed.push(substate);
+      const new_substate_info = getSubstateFromUpdate(
+        update.delta[substate],
+        substate
+      );
+      if (new_substate_info === null) {
+        console.error("Received patch out of order", update.delta[substate]);
+        failed_substates.push(substate);
+        delete update.delta[substate];
         continue;
       }
       last_substate_info[substate] = new_substate_info;
+      update.delta[substate] = new_substate_info;
       dispatch[substate](new_substate_info);
     }
-    // TODO: Handle failed states
     applyClientStorageDelta(client_storage, update.delta);
     event_processing = !update.final;
+    if (failed_substates.length > 0) {
+      update.events.push(
+        Event(state_name + ".partial_hydrate", { states: failed_substates })
+      );
+    }
     if (update.events) {
       queueEvents(update.events, socket);
     }
@@ -919,7 +925,7 @@ export const useEventLoop = (
   // Route after the initial page hydration.
   useEffect(() => {
     const change_start = () => {
-      const main_state_dispatch = dispatch["reflex___state____state"];
+      const main_state_dispatch = dispatch[state_name];
       if (main_state_dispatch !== undefined) {
         main_state_dispatch({ is_hydrated: false });
       }

+ 2 - 0
reflex/app_mixins/middleware.py

@@ -7,6 +7,7 @@ import dataclasses
 
 from reflex.event import Event
 from reflex.middleware import HydrateMiddleware, Middleware
+from reflex.middleware.hydrate_middleware import PartialHyderateMiddleware
 from reflex.state import BaseState, StateUpdate
 
 from .mixin import AppMixin
@@ -21,6 +22,7 @@ class MiddlewareMixin(AppMixin):
 
     def _init_mixin(self):
         self.middleware.append(HydrateMiddleware())
+        self.middleware.append(PartialHyderateMiddleware())
 
     def add_middleware(self, middleware: Middleware, index: int | None = None):
         """Add middleware to the app.

+ 3 - 2
reflex/middleware/__init__.py

@@ -1,4 +1,5 @@
 """Reflex middleware."""
 
-from .hydrate_middleware import HydrateMiddleware
-from .middleware import Middleware
+from .hydrate_middleware import HydrateMiddleware as HydrateMiddleware
+from .hydrate_middleware import PartialHyderateMiddleware as PartialHydrateMiddleware
+from .middleware import Middleware as Middleware

+ 1 - 1
reflex/middleware/hydrate_middleware.py

@@ -84,7 +84,7 @@ class PartialHyderateMiddleware(Middleware):
         substates = [
             substate
             for substate_name in substates_names
-            if (substate := state.get_substate(substate_name)) is not None
+            if (substate := state.get_substate(substate_name.split("."))) is not None
         ]
 
         delta = await _resolve_delta(

+ 7 - 8
reflex/state.py

@@ -197,7 +197,7 @@ class DeltaCache(NamedTuple):
     """A named tuple representing the delta cache."""
 
     hash: int
-    delta: StateDelta
+    delta: dict[str, Any]
 
 
 LAST_DELTA_CACHE: dict[str, DeltaCache] = {}
@@ -221,10 +221,13 @@ def serialize_state_delta(delta: StateDelta) -> dict[str, Any]:
             key = delta.client_token + state_name
             cached = LAST_DELTA_CACHE.get(key)
             hash_value = hash(json_str)
-            LAST_DELTA_CACHE[key] = DeltaCache(hash_value, delta)
+            LAST_DELTA_CACHE[key] = DeltaCache(hash_value, new_state_value)
             if cached is not None and not delta.flush:
+                patch = make_patch(cached.delta, new_state_value).patch
+                if not patch:
+                    continue
                 full_delta[state_name] = {
-                    "__patch": make_patch(cached.delta.data, new_state_value).patch,
+                    "__patch": patch,
                     "__previous_hash": cached.hash,
                     "__hash": hash_value,
                 }
@@ -2031,11 +2034,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             name for name, cv in self.computed_vars.items() if not cv._backend
         }
 
-        # Return the dirty vars for this instance, any cached/dependent computed vars,
-        # and always dirty computed vars (cache=False)
-        delta_vars = self.dirty_vars.intersection(self.base_vars).union(
-            self.dirty_vars.intersection(frontend_computed_vars)
-        )
+        delta_vars = frontend_computed_vars.union(self.base_vars)
 
         subdelta: dict[str, Any] = {
             prop: self.get_value(prop)