reflex 0.7.14a6__py3-none-any.whl → 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (237) hide show
  1. reflex/.templates/jinja/app/rxconfig.py.jinja2 +4 -1
  2. reflex/.templates/jinja/web/package.json.jinja2 +1 -1
  3. reflex/.templates/jinja/web/pages/_app.js.jinja2 +21 -11
  4. reflex/.templates/jinja/web/pages/_document.js.jinja2 +1 -1
  5. reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -1
  6. reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +4 -0
  7. reflex/.templates/jinja/web/styles/styles.css.jinja2 +1 -0
  8. reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -8
  9. reflex/.templates/web/app/entry.client.js +8 -0
  10. reflex/.templates/web/app/routes.js +10 -0
  11. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -37
  12. reflex/.templates/web/postcss.config.js +1 -1
  13. reflex/.templates/web/react-router.config.js +6 -0
  14. reflex/.templates/web/styles/__reflex_style_reset.css +399 -0
  15. reflex/.templates/web/utils/client_side_routing.js +21 -19
  16. reflex/.templates/web/utils/react-theme.js +92 -0
  17. reflex/.templates/web/utils/state.js +251 -100
  18. reflex/.templates/web/vite-plugin-safari-cachebust.js +160 -0
  19. reflex/.templates/web/vite.config.js +39 -0
  20. reflex/__init__.py +1 -6
  21. reflex/__init__.pyi +327 -192
  22. reflex/app.py +86 -135
  23. reflex/base.py +1 -87
  24. reflex/compiler/compiler.py +70 -19
  25. reflex/compiler/templates.py +3 -3
  26. reflex/compiler/utils.py +91 -33
  27. reflex/components/__init__.py +0 -2
  28. reflex/components/__init__.pyi +34 -18
  29. reflex/components/base/__init__.py +1 -5
  30. reflex/components/base/__init__.pyi +30 -21
  31. reflex/components/base/app_wrap.pyi +7 -7
  32. reflex/components/base/body.pyi +7 -7
  33. reflex/components/base/document.py +18 -14
  34. reflex/components/base/document.pyi +88 -38
  35. reflex/components/base/error_boundary.pyi +7 -7
  36. reflex/components/base/fragment.pyi +7 -7
  37. reflex/components/base/link.pyi +12 -12
  38. reflex/components/base/meta.py +4 -15
  39. reflex/components/base/meta.pyi +31 -31
  40. reflex/components/base/script.py +60 -58
  41. reflex/components/base/script.pyi +248 -34
  42. reflex/components/base/strict_mode.pyi +7 -7
  43. reflex/components/component.py +146 -217
  44. reflex/components/core/__init__.py +1 -0
  45. reflex/components/core/__init__.pyi +77 -37
  46. reflex/components/core/auto_scroll.pyi +7 -7
  47. reflex/components/core/banner.pyi +33 -33
  48. reflex/components/core/client_side_routing.py +7 -6
  49. reflex/components/core/client_side_routing.pyi +8 -59
  50. reflex/components/core/clipboard.pyi +7 -7
  51. reflex/components/core/debounce.py +1 -0
  52. reflex/components/core/debounce.pyi +7 -7
  53. reflex/components/core/foreach.py +5 -4
  54. reflex/components/core/helmet.py +14 -0
  55. reflex/components/{next/base.pyi → core/helmet.pyi} +12 -10
  56. reflex/components/core/html.pyi +7 -7
  57. reflex/components/core/match.py +3 -3
  58. reflex/components/core/sticky.pyi +21 -20
  59. reflex/components/core/upload.py +4 -2
  60. reflex/components/core/upload.pyi +26 -25
  61. reflex/components/datadisplay/__init__.pyi +13 -7
  62. reflex/components/datadisplay/code.py +14 -79
  63. reflex/components/datadisplay/code.pyi +11 -13
  64. reflex/components/datadisplay/dataeditor.pyi +38 -15
  65. reflex/components/datadisplay/shiki_code_block.py +5 -3
  66. reflex/components/datadisplay/shiki_code_block.pyi +16 -15
  67. reflex/components/dynamic.py +5 -5
  68. reflex/components/el/__init__.pyi +506 -246
  69. reflex/components/el/element.pyi +7 -7
  70. reflex/components/el/elements/__init__.pyi +504 -245
  71. reflex/components/el/elements/base.pyi +7 -7
  72. reflex/components/el/elements/forms.pyi +146 -101
  73. reflex/components/el/elements/inline.pyi +142 -142
  74. reflex/components/el/elements/media.pyi +131 -130
  75. reflex/components/el/elements/metadata.pyi +32 -32
  76. reflex/components/el/elements/other.pyi +37 -37
  77. reflex/components/el/elements/scripts.pyi +17 -17
  78. reflex/components/el/elements/sectioning.pyi +77 -77
  79. reflex/components/el/elements/tables.pyi +52 -52
  80. reflex/components/el/elements/typography.pyi +77 -77
  81. reflex/components/field.py +175 -0
  82. reflex/components/gridjs/datatable.py +2 -2
  83. reflex/components/gridjs/datatable.pyi +14 -14
  84. reflex/components/lucide/icon.py +6 -2
  85. reflex/components/lucide/icon.pyi +19 -17
  86. reflex/components/markdown/markdown.py +5 -3
  87. reflex/components/markdown/markdown.pyi +7 -7
  88. reflex/components/moment/moment.py +1 -1
  89. reflex/components/moment/moment.pyi +7 -7
  90. reflex/components/plotly/plotly.py +12 -6
  91. reflex/components/plotly/plotly.pyi +50 -49
  92. reflex/components/props.py +376 -27
  93. reflex/components/radix/__init__.pyi +123 -65
  94. reflex/components/radix/primitives/__init__.pyi +6 -4
  95. reflex/components/radix/primitives/accordion.py +8 -1
  96. reflex/components/radix/primitives/accordion.pyi +37 -37
  97. reflex/components/radix/primitives/base.pyi +12 -12
  98. reflex/components/radix/primitives/drawer.pyi +56 -55
  99. reflex/components/radix/primitives/form.pyi +63 -53
  100. reflex/components/radix/primitives/progress.pyi +26 -25
  101. reflex/components/radix/primitives/slider.pyi +27 -27
  102. reflex/components/radix/themes/__init__.pyi +5 -6
  103. reflex/components/radix/themes/base.py +3 -3
  104. reflex/components/radix/themes/base.pyi +42 -42
  105. reflex/components/radix/themes/color_mode.py +5 -6
  106. reflex/components/radix/themes/color_mode.pyi +17 -17
  107. reflex/components/radix/themes/components/__init__.pyi +75 -38
  108. reflex/components/radix/themes/components/alert_dialog.pyi +37 -37
  109. reflex/components/radix/themes/components/aspect_ratio.pyi +7 -7
  110. reflex/components/radix/themes/components/avatar.pyi +7 -7
  111. reflex/components/radix/themes/components/badge.pyi +7 -7
  112. reflex/components/radix/themes/components/button.pyi +7 -7
  113. reflex/components/radix/themes/components/callout.pyi +26 -25
  114. reflex/components/radix/themes/components/card.pyi +7 -7
  115. reflex/components/radix/themes/components/checkbox.pyi +16 -15
  116. reflex/components/radix/themes/components/checkbox_cards.pyi +12 -12
  117. reflex/components/radix/themes/components/checkbox_group.pyi +12 -12
  118. reflex/components/radix/themes/components/context_menu.pyi +67 -67
  119. reflex/components/radix/themes/components/data_list.pyi +22 -22
  120. reflex/components/radix/themes/components/dialog.pyi +36 -35
  121. reflex/components/radix/themes/components/dropdown_menu.pyi +42 -42
  122. reflex/components/radix/themes/components/hover_card.pyi +21 -20
  123. reflex/components/radix/themes/components/icon_button.pyi +7 -7
  124. reflex/components/radix/themes/components/inset.pyi +7 -7
  125. reflex/components/radix/themes/components/popover.pyi +22 -22
  126. reflex/components/radix/themes/components/progress.pyi +7 -7
  127. reflex/components/radix/themes/components/radio.pyi +7 -7
  128. reflex/components/radix/themes/components/radio_cards.pyi +12 -12
  129. reflex/components/radix/themes/components/radio_group.pyi +21 -20
  130. reflex/components/radix/themes/components/scroll_area.pyi +7 -7
  131. reflex/components/radix/themes/components/segmented_control.pyi +12 -12
  132. reflex/components/radix/themes/components/select.pyi +46 -45
  133. reflex/components/radix/themes/components/separator.pyi +7 -7
  134. reflex/components/radix/themes/components/skeleton.pyi +7 -7
  135. reflex/components/radix/themes/components/slider.pyi +17 -9
  136. reflex/components/radix/themes/components/spinner.pyi +7 -7
  137. reflex/components/radix/themes/components/switch.pyi +7 -7
  138. reflex/components/radix/themes/components/table.pyi +37 -37
  139. reflex/components/radix/themes/components/tabs.pyi +26 -25
  140. reflex/components/radix/themes/components/text_area.pyi +15 -9
  141. reflex/components/radix/themes/components/text_field.pyi +32 -19
  142. reflex/components/radix/themes/components/tooltip.pyi +7 -7
  143. reflex/components/radix/themes/layout/__init__.pyi +27 -14
  144. reflex/components/radix/themes/layout/base.pyi +7 -7
  145. reflex/components/radix/themes/layout/box.pyi +7 -7
  146. reflex/components/radix/themes/layout/center.pyi +7 -7
  147. reflex/components/radix/themes/layout/container.pyi +7 -7
  148. reflex/components/radix/themes/layout/flex.pyi +7 -7
  149. reflex/components/radix/themes/layout/grid.pyi +7 -7
  150. reflex/components/radix/themes/layout/list.pyi +26 -25
  151. reflex/components/radix/themes/layout/section.pyi +7 -7
  152. reflex/components/radix/themes/layout/spacer.pyi +7 -7
  153. reflex/components/radix/themes/layout/stack.pyi +17 -17
  154. reflex/components/radix/themes/typography/__init__.pyi +7 -5
  155. reflex/components/radix/themes/typography/blockquote.pyi +7 -7
  156. reflex/components/radix/themes/typography/code.pyi +7 -7
  157. reflex/components/radix/themes/typography/heading.pyi +7 -7
  158. reflex/components/radix/themes/typography/link.py +46 -11
  159. reflex/components/radix/themes/typography/link.pyi +312 -9
  160. reflex/components/radix/themes/typography/text.pyi +36 -35
  161. reflex/components/react_player/audio.pyi +10 -8
  162. reflex/components/react_player/react_player.pyi +7 -7
  163. reflex/components/react_player/video.pyi +10 -8
  164. reflex/components/recharts/__init__.pyi +208 -100
  165. reflex/components/recharts/cartesian.py +10 -8
  166. reflex/components/recharts/cartesian.pyi +90 -94
  167. reflex/components/recharts/charts.py +4 -2
  168. reflex/components/recharts/charts.pyi +49 -49
  169. reflex/components/recharts/general.pyi +31 -31
  170. reflex/components/recharts/polar.py +8 -4
  171. reflex/components/recharts/polar.pyi +23 -23
  172. reflex/components/recharts/recharts.py +2 -2
  173. reflex/components/recharts/recharts.pyi +12 -12
  174. reflex/components/sonner/toast.py +3 -3
  175. reflex/components/sonner/toast.pyi +9 -9
  176. reflex/config.py +10 -113
  177. reflex/constants/__init__.py +2 -2
  178. reflex/constants/base.py +28 -11
  179. reflex/constants/compiler.py +12 -3
  180. reflex/constants/event.py +1 -0
  181. reflex/constants/installer.py +26 -20
  182. reflex/constants/route.py +27 -8
  183. reflex/constants/state.py +2 -0
  184. reflex/custom_components/custom_components.py +0 -14
  185. reflex/environment.py +77 -5
  186. reflex/event.py +178 -81
  187. reflex/experimental/__init__.py +0 -30
  188. reflex/istate/__init__.py +69 -0
  189. reflex/istate/manager.py +1 -0
  190. reflex/istate/proxy.py +5 -3
  191. reflex/page.py +0 -27
  192. reflex/plugins/__init__.py +3 -2
  193. reflex/plugins/base.py +5 -1
  194. reflex/plugins/shared_tailwind.py +215 -0
  195. reflex/plugins/sitemap.py +206 -0
  196. reflex/plugins/tailwind_v3.py +15 -108
  197. reflex/plugins/tailwind_v4.py +18 -110
  198. reflex/reflex.py +1 -0
  199. reflex/route.py +157 -75
  200. reflex/state.py +171 -155
  201. reflex/testing.py +86 -16
  202. reflex/utils/build.py +38 -82
  203. reflex/utils/exec.py +83 -175
  204. reflex/utils/export.py +2 -2
  205. reflex/utils/format.py +1 -5
  206. reflex/utils/imports.py +5 -16
  207. reflex/utils/misc.py +67 -0
  208. reflex/utils/prerequisites.py +66 -68
  209. reflex/utils/processes.py +24 -47
  210. reflex/utils/pyi_generator.py +44 -49
  211. reflex/utils/serializers.py +14 -1
  212. reflex/utils/telemetry.py +0 -15
  213. reflex/utils/types.py +197 -62
  214. reflex/vars/__init__.py +2 -0
  215. reflex/vars/base.py +367 -134
  216. {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/METADATA +15 -8
  217. reflex-0.8.0.dist-info/RECORD +403 -0
  218. reflex/.templates/web/next.config.js +0 -7
  219. reflex/components/base/head.py +0 -20
  220. reflex/components/base/head.pyi +0 -116
  221. reflex/components/next/__init__.py +0 -10
  222. reflex/components/next/base.py +0 -7
  223. reflex/components/next/image.py +0 -117
  224. reflex/components/next/image.pyi +0 -94
  225. reflex/components/next/link.py +0 -20
  226. reflex/components/next/link.pyi +0 -67
  227. reflex/components/next/video.py +0 -38
  228. reflex/components/next/video.pyi +0 -68
  229. reflex/components/suneditor/__init__.py +0 -5
  230. reflex/components/suneditor/editor.py +0 -269
  231. reflex/components/suneditor/editor.pyi +0 -199
  232. reflex/experimental/layout.py +0 -254
  233. reflex/experimental/layout.pyi +0 -814
  234. reflex-0.7.14a6.dist-info/RECORD +0 -408
  235. {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/WHEEL +0 -0
  236. {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/entry_points.txt +0 -0
  237. {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,23 @@
1
1
  // State management for Reflex web apps.
2
- import axios from "axios";
3
2
  import io from "socket.io-client";
4
3
  import JSON5 from "json5";
5
4
  import env from "$/env.json";
6
5
  import reflexEnvironment from "$/reflex.json";
7
6
  import Cookies from "universal-cookie";
8
- import { useEffect, useRef, useState } from "react";
9
- import Router, { useRouter } from "next/router";
7
+ import { useCallback, useEffect, useRef, useState } from "react";
8
+ import {
9
+ useLocation,
10
+ useNavigate,
11
+ useSearchParams,
12
+ useParams,
13
+ } from "react-router";
10
14
  import {
11
15
  initialEvents,
12
16
  initialState,
13
17
  onLoadInternalEvent,
14
18
  state_name,
15
19
  exception_state_name,
16
- } from "$/utils/context.js";
20
+ } from "$/utils/context";
17
21
  import debounce from "$/utils/helpers/debounce";
18
22
  import throttle from "$/utils/helpers/throttle";
19
23
 
@@ -158,24 +162,48 @@ export const evalReactComponent = async (component) => {
158
162
  * Only Queue and process events when websocket connection exists.
159
163
  * @param event The event to queue.
160
164
  * @param socket The socket object to send the event on.
165
+ * @param navigate The navigate function from React Router
166
+ * @param params The params object from React Router
161
167
  *
162
168
  * @returns Adds event to queue and processes it if websocket exits, does nothing otherwise.
163
169
  */
164
- export const queueEventIfSocketExists = async (events, socket) => {
170
+ export const queueEventIfSocketExists = async (
171
+ events,
172
+ socket,
173
+ navigate,
174
+ params,
175
+ ) => {
165
176
  if (!socket) {
166
177
  return;
167
178
  }
168
- await queueEvents(events, socket);
179
+ await queueEvents(events, socket, navigate, params);
169
180
  };
170
181
 
182
+ /**
183
+ * Check if a string is a valid HTTP URL.
184
+ * @param string The string to check.
185
+ *
186
+ * @returns The URL object if valid, undefined otherwise.
187
+ * */
188
+ function urlFrom(string) {
189
+ try {
190
+ return new URL(string);
191
+ } catch {
192
+ return undefined;
193
+ }
194
+ return undefined;
195
+ }
196
+
171
197
  /**
172
198
  * Handle frontend event or send the event to the backend via Websocket.
173
199
  * @param event The event to send.
174
200
  * @param socket The socket object to send the event on.
201
+ * @param navigate The navigate function from useNavigate
202
+ * @param params The params object from useParams
175
203
  *
176
204
  * @returns True if the event was sent, false if it was handled locally.
177
205
  */
178
- export const applyEvent = async (event, socket) => {
206
+ export const applyEvent = async (event, socket, navigate, params) => {
179
207
  // Handle special events
180
208
  if (event.name == "_redirect") {
181
209
  if ((event.payload.path ?? undefined) === undefined) {
@@ -183,41 +211,54 @@ export const applyEvent = async (event, socket) => {
183
211
  }
184
212
  if (event.payload.external) {
185
213
  window.open(event.payload.path, "_blank", "noopener");
186
- } else if (event.payload.replace) {
187
- Router.replace(event.payload.path);
214
+ return false;
215
+ }
216
+ const url = urlFrom(event.payload.path);
217
+ let pathname = event.payload.path;
218
+ if (url) {
219
+ if (url.host !== window.location.host) {
220
+ // External URL
221
+ window.location.assign(event.payload.path);
222
+ return false;
223
+ } else {
224
+ pathname = url.pathname + url.search + url.hash;
225
+ }
226
+ }
227
+ if (event.payload.replace) {
228
+ navigate(pathname, { replace: true });
188
229
  } else {
189
- Router.push(event.payload.path);
230
+ navigate(pathname);
190
231
  }
191
232
  return false;
192
233
  }
193
234
 
194
235
  if (event.name == "_remove_cookie") {
195
236
  cookies.remove(event.payload.key, { ...event.payload.options });
196
- queueEventIfSocketExists(initialEvents(), socket);
237
+ queueEventIfSocketExists(initialEvents(), socket, navigate, params);
197
238
  return false;
198
239
  }
199
240
 
200
241
  if (event.name == "_clear_local_storage") {
201
242
  localStorage.clear();
202
- queueEventIfSocketExists(initialEvents(), socket);
243
+ queueEventIfSocketExists(initialEvents(), socket, navigate, params);
203
244
  return false;
204
245
  }
205
246
 
206
247
  if (event.name == "_remove_local_storage") {
207
248
  localStorage.removeItem(event.payload.key);
208
- queueEventIfSocketExists(initialEvents(), socket);
249
+ queueEventIfSocketExists(initialEvents(), socket, navigate, params);
209
250
  return false;
210
251
  }
211
252
 
212
253
  if (event.name == "_clear_session_storage") {
213
254
  sessionStorage.clear();
214
- queueEvents(initialEvents(), socket);
255
+ queueEvents(initialEvents(), socket, navigate, params);
215
256
  return false;
216
257
  }
217
258
 
218
259
  if (event.name == "_remove_session_storage") {
219
260
  sessionStorage.removeItem(event.payload.key);
220
- queueEvents(initialEvents(), socket);
261
+ queueEvents(initialEvents(), socket, navigate, params);
221
262
  return false;
222
263
  }
223
264
 
@@ -322,11 +363,15 @@ export const applyEvent = async (event, socket) => {
322
363
  event.router_data === undefined ||
323
364
  Object.keys(event.router_data).length === 0
324
365
  ) {
325
- event.router_data = (({ pathname, query, asPath }) => ({
326
- pathname,
327
- query,
328
- asPath,
329
- }))(Router);
366
+ // Since we don't have router directly, we need to get info from our hooks
367
+ event.router_data = {
368
+ pathname: window.location.pathname,
369
+ query: {
370
+ ...Object.fromEntries(new URLSearchParams(window.location.search)),
371
+ ...params(),
372
+ },
373
+ asPath: window.location.pathname + window.location.search,
374
+ };
330
375
  }
331
376
 
332
377
  // Send the event to the server.
@@ -342,15 +387,22 @@ export const applyEvent = async (event, socket) => {
342
387
  * Send an event to the server via REST.
343
388
  * @param event The current event.
344
389
  * @param socket The socket object to send the response event(s) on.
390
+ * @param navigate The navigate function from React Router
391
+ * @param params The params object from React Router
345
392
  *
346
393
  * @returns Whether the event was sent.
347
394
  */
348
- export const applyRestEvent = async (event, socket) => {
395
+ export const applyRestEvent = async (event, socket, navigate, params) => {
349
396
  let eventSent = false;
350
397
  if (event.handler === "uploadFiles") {
351
398
  if (event.payload.files === undefined || event.payload.files.length === 0) {
352
399
  // Submit the event over the websocket to trigger the event handler.
353
- return await applyEvent(Event(event.name, { files: [] }), socket);
400
+ return await applyEvent(
401
+ Event(event.name, { files: [] }),
402
+ socket,
403
+ navigate,
404
+ params,
405
+ );
354
406
  }
355
407
 
356
408
  // Start upload, but do not wait for it, which would block other events.
@@ -371,8 +423,16 @@ export const applyRestEvent = async (event, socket) => {
371
423
  * @param events Array of events to queue.
372
424
  * @param socket The socket object to send the event on.
373
425
  * @param prepend Whether to place the events at the beginning of the queue.
426
+ * @param navigate The navigate function from React Router
427
+ * @param params The params object from React Router
374
428
  */
375
- export const queueEvents = async (events, socket, prepend) => {
429
+ export const queueEvents = async (
430
+ events,
431
+ socket,
432
+ prepend,
433
+ navigate,
434
+ params,
435
+ ) => {
376
436
  if (prepend) {
377
437
  // Drain the existing queue and place it after the given events.
378
438
  events = [
@@ -383,14 +443,16 @@ export const queueEvents = async (events, socket, prepend) => {
383
443
  ];
384
444
  }
385
445
  event_queue.push(...events.filter((e) => e !== undefined && e !== null));
386
- await processEvent(socket.current);
446
+ await processEvent(socket.current, navigate, params);
387
447
  };
388
448
 
389
449
  /**
390
450
  * Process an event off the event queue.
391
451
  * @param socket The socket object to send the event on.
452
+ * @param navigate The navigate function from React Router
453
+ * @param params The params object from React Router
392
454
  */
393
- export const processEvent = async (socket) => {
455
+ export const processEvent = async (socket, navigate, params) => {
394
456
  // Only proceed if the socket is up and no event in the queue uses state, otherwise we throw the event into the void
395
457
  if (!socket && isStateful()) {
396
458
  return;
@@ -410,16 +472,16 @@ export const processEvent = async (socket) => {
410
472
  let eventSent = false;
411
473
  // Process events with handlers via REST and all others via websockets.
412
474
  if (event.handler) {
413
- eventSent = await applyRestEvent(event, socket);
475
+ eventSent = await applyRestEvent(event, socket, navigate, params);
414
476
  } else {
415
- eventSent = await applyEvent(event, socket);
477
+ eventSent = await applyEvent(event, socket, navigate, params);
416
478
  }
417
479
  // If no event was sent, set processing to false.
418
480
  if (!eventSent) {
419
481
  event_processing = false;
420
482
  // recursively call processEvent to drain the queue, since there is
421
483
  // no state update to trigger the useEffect event loop.
422
- await processEvent(socket);
484
+ await processEvent(socket, navigate, params);
423
485
  }
424
486
  };
425
487
 
@@ -430,6 +492,8 @@ export const processEvent = async (socket) => {
430
492
  * @param transports The transports to use.
431
493
  * @param setConnectErrors The function to update connection error value.
432
494
  * @param client_storage The client storage object from context.js
495
+ * @param navigate The navigate function from React Router
496
+ * @param params The params object from React Router
433
497
  */
434
498
  export const connect = async (
435
499
  socket,
@@ -437,6 +501,8 @@ export const connect = async (
437
501
  transports,
438
502
  setConnectErrors,
439
503
  client_storage = {},
504
+ navigate,
505
+ params,
440
506
  ) => {
441
507
  // Get backend URL object from the endpoint.
442
508
  const endpoint = getBackendURL(EVENTURL);
@@ -511,12 +577,12 @@ export const connect = async (
511
577
  applyClientStorageDelta(client_storage, update.delta);
512
578
  event_processing = !update.final;
513
579
  if (update.events) {
514
- queueEvents(update.events, socket);
580
+ queueEvents(update.events, socket, false, navigate, params);
515
581
  }
516
582
  });
517
583
  socket.current.on("reload", async (event) => {
518
584
  event_processing = false;
519
- queueEvents([...initialEvents(), event], socket, true);
585
+ queueEvents([...initialEvents(), event], socket, true, navigate, params);
520
586
  });
521
587
 
522
588
  document.addEventListener("visibilitychange", checkVisibility);
@@ -588,17 +654,6 @@ export const uploadFiles = async (
588
654
  };
589
655
 
590
656
  const controller = new AbortController();
591
- const config = {
592
- headers: {
593
- "Reflex-Client-Token": getToken(),
594
- "Reflex-Event-Handler": handler,
595
- },
596
- signal: controller.signal,
597
- onDownloadProgress: eventHandler,
598
- };
599
- if (on_upload_progress) {
600
- config["onUploadProgress"] = on_upload_progress;
601
- }
602
657
  const formdata = new FormData();
603
658
 
604
659
  // Add the token and handler to the file name.
@@ -609,26 +664,85 @@ export const uploadFiles = async (
609
664
  // Send the file to the server.
610
665
  refs[upload_ref_name] = controller;
611
666
 
612
- try {
613
- return await axios.post(getBackendURL(UPLOADURL), formdata, config);
614
- } catch (error) {
615
- if (error.response) {
616
- // The request was made and the server responded with a status code
617
- // that falls out of the range of 2xx
618
- console.log(error.response.data);
619
- } else if (error.request) {
620
- // The request was made but no response was received
621
- // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
622
- // http.ClientRequest in node.js
623
- console.log(error.request);
624
- } else {
625
- // Something happened in setting up the request that triggered an Error
626
- console.log(error.message);
667
+ return new Promise((resolve, reject) => {
668
+ const xhr = new XMLHttpRequest();
669
+
670
+ // Set up event handlers
671
+ xhr.onload = function () {
672
+ if (xhr.status >= 200 && xhr.status < 300) {
673
+ resolve({
674
+ data: xhr.responseText,
675
+ status: xhr.status,
676
+ statusText: xhr.statusText,
677
+ headers: {
678
+ get: (name) => xhr.getResponseHeader(name),
679
+ },
680
+ });
681
+ } else {
682
+ reject(new Error(`HTTP error! status: ${xhr.status}`));
683
+ }
684
+ };
685
+
686
+ xhr.onerror = function () {
687
+ reject(new Error("Network error"));
688
+ };
689
+
690
+ xhr.onabort = function () {
691
+ reject(new Error("Upload aborted"));
692
+ };
693
+
694
+ // Handle upload progress
695
+ if (on_upload_progress) {
696
+ xhr.upload.onprogress = function (event) {
697
+ if (event.lengthComputable) {
698
+ const progressEvent = {
699
+ loaded: event.loaded,
700
+ total: event.total,
701
+ progress: event.loaded / event.total,
702
+ };
703
+ on_upload_progress(progressEvent);
704
+ }
705
+ };
627
706
  }
628
- return false;
629
- } finally {
630
- delete refs[upload_ref_name];
631
- }
707
+
708
+ // Handle download progress with streaming response parsing
709
+ xhr.onprogress = function (event) {
710
+ if (eventHandler) {
711
+ const progressEvent = {
712
+ event: {
713
+ target: {
714
+ responseText: xhr.responseText,
715
+ },
716
+ },
717
+ progress: event.lengthComputable ? event.loaded / event.total : 0,
718
+ };
719
+ eventHandler(progressEvent);
720
+ }
721
+ };
722
+
723
+ // Handle abort controller
724
+ controller.signal.addEventListener("abort", () => {
725
+ xhr.abort();
726
+ });
727
+
728
+ // Configure and send request
729
+ xhr.open("POST", getBackendURL(UPLOADURL));
730
+ xhr.setRequestHeader("Reflex-Client-Token", getToken());
731
+ xhr.setRequestHeader("Reflex-Event-Handler", handler);
732
+
733
+ try {
734
+ xhr.send(formdata);
735
+ } catch (error) {
736
+ reject(error);
737
+ }
738
+ })
739
+ .catch((error) => {
740
+ console.log("Upload error:", error.message);
741
+ return false;
742
+ })
743
+ .finally(() => {
744
+ delete refs[upload_ref_name];
745
+ });
632
746
  };
633
747
 
634
748
  /**
@@ -710,7 +824,10 @@ const applyClientStorageDelta = (client_storage, delta) => {
710
824
  );
711
825
  if (unqualified_states.length === 1) {
712
826
  const main_state = delta[unqualified_states[0]];
713
- if (main_state.is_hydrated !== undefined && !main_state.is_hydrated) {
827
+ if (
828
+ main_state.is_hydrated_rx_state_ !== undefined &&
829
+ !main_state.is_hydrated_rx_state_
830
+ ) {
714
831
  // skip if the state is not hydrated yet, since all client storage
715
832
  // values are sent in the hydrate event
716
833
  return;
@@ -748,7 +865,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
748
865
  };
749
866
 
750
867
  /**
751
- * Establish websocket event loop for a NextJS page.
868
+ * Establish websocket event loop for a React Router page.
752
869
  * @param dispatch The reducer dispatch function to update state.
753
870
  * @param initial_events The initial app events.
754
871
  * @param client_storage The client storage object from context.js
@@ -763,11 +880,25 @@ export const useEventLoop = (
763
880
  client_storage = {},
764
881
  ) => {
765
882
  const socket = useRef(null);
766
- const router = useRouter();
883
+ const location = useLocation();
884
+ const navigate = useNavigate();
885
+ const paramsR = useParams();
886
+ const prevLocationRef = useRef(location);
887
+ const [searchParams] = useSearchParams();
767
888
  const [connectErrors, setConnectErrors] = useState([]);
889
+ const params = useRef(paramsR);
890
+
891
+ useEffect(() => {
892
+ const { "*": splat, ...remainingParams } = paramsR;
893
+ if (splat) {
894
+ params.current = { ...remainingParams, splat: splat.split("/") };
895
+ } else {
896
+ params.current = remainingParams;
897
+ }
898
+ }, [paramsR]);
768
899
 
769
900
  // Function to add new events to the event queue.
770
- const addEvents = (events, args, event_actions) => {
901
+ const addEvents = useCallback((events, args, event_actions) => {
771
902
  const _events = events.filter((e) => e !== undefined && e !== null);
772
903
 
773
904
  if (!(args instanceof Array)) {
@@ -803,32 +934,38 @@ export const useEventLoop = (
803
934
  // If debounce is used, queue the events after some delay
804
935
  debounce(
805
936
  combined_name,
806
- () => queueEvents(_events, socket),
937
+ () =>
938
+ queueEvents(_events, socket, false, navigate, () => params.current),
807
939
  event_actions.debounce,
808
940
  );
809
941
  } else {
810
- queueEvents(_events, socket);
942
+ queueEvents(_events, socket, false, navigate, () => params.current);
811
943
  }
812
- };
944
+ }, []);
813
945
 
814
946
  const sentHydrate = useRef(false); // Avoid double-hydrate due to React strict-mode
815
947
  useEffect(() => {
816
- if (router.isReady && !sentHydrate.current) {
948
+ if (!sentHydrate.current) {
817
949
  queueEvents(
818
950
  initial_events().map((e) => ({
819
951
  ...e,
820
- router_data: (({ pathname, query, asPath }) => ({
821
- pathname,
822
- query,
823
- asPath,
824
- }))(router),
952
+ router_data: {
953
+ pathname: location.pathname,
954
+ query: {
955
+ ...Object.fromEntries(searchParams.entries()),
956
+ ...params.current,
957
+ },
958
+ asPath: location.pathname + location.search,
959
+ },
825
960
  })),
826
961
  socket,
827
962
  true,
963
+ navigate,
964
+ () => params.current,
828
965
  );
829
966
  sentHydrate.current = true;
830
967
  }
831
- }, [router.isReady]);
968
+ }, []);
832
969
 
833
970
  // Handle frontend errors and send them to the backend via websocket.
834
971
  useEffect(() => {
@@ -871,6 +1008,8 @@ export const useEventLoop = (
871
1008
  ["websocket"],
872
1009
  setConnectErrors,
873
1010
  client_storage,
1011
+ navigate,
1012
+ () => params.current,
874
1013
  );
875
1014
  }
876
1015
  }
@@ -885,14 +1024,14 @@ export const useEventLoop = (
885
1024
 
886
1025
  // Main event loop.
887
1026
  useEffect(() => {
888
- // Skip if the router is not ready.
889
- if (!router.isReady || isBackendDisabled()) {
1027
+ // Skip if the backend is disabled
1028
+ if (isBackendDisabled()) {
890
1029
  return;
891
1030
  }
892
1031
  (async () => {
893
1032
  // Process all outstanding events.
894
1033
  while (event_queue.length > 0 && !event_processing) {
895
- await processEvent(socket.current);
1034
+ await processEvent(socket.current, navigate, () => params.current);
896
1035
  }
897
1036
  })();
898
1037
  });
@@ -928,31 +1067,43 @@ export const useEventLoop = (
928
1067
  return () => window.removeEventListener("storage", handleStorage);
929
1068
  });
930
1069
 
931
- // Route after the initial page hydration.
1070
+ const handleNavigationEvents = useRef(false);
1071
+ // Route after the initial page hydration
932
1072
  useEffect(() => {
933
- const change_start = () => {
934
- const main_state_dispatch = dispatch["reflex___state____state"];
935
- if (main_state_dispatch !== undefined) {
936
- main_state_dispatch({ is_hydrated: false });
937
- }
938
- };
939
- const change_complete = () => addEvents(onLoadInternalEvent());
940
- const change_error = () => {
941
- // Remove cached error state from router for this page, otherwise the
942
- // page will never send on_load events again.
943
- if (router.components[router.pathname].error) {
944
- delete router.components[router.pathname].error;
1073
+ // The first time this effect runs is initial load, so don't handle
1074
+ // any navigation events.
1075
+ if (!handleNavigationEvents.current) {
1076
+ handleNavigationEvents.current = true;
1077
+ return;
1078
+ }
1079
+ if (location.state?.fromNotFound) {
1080
+ // If the redirect is from a 404 page, we skip onLoadInternalEvent,
1081
+ // since it was already run when the 404 page was first rendered.
1082
+ return;
1083
+ }
1084
+ // This will run when the location changes
1085
+ if (
1086
+ location.pathname + location.search ===
1087
+ prevLocationRef.current.pathname + prevLocationRef.current.search
1088
+ ) {
1089
+ if (location.hash) {
1090
+ // If the hash is the same, we don't need to do anything.
1091
+ return;
945
1092
  }
946
- };
947
- router.events.on("routeChangeStart", change_start);
948
- router.events.on("routeChangeComplete", change_complete);
949
- router.events.on("routeChangeError", change_error);
950
- return () => {
951
- router.events.off("routeChangeStart", change_start);
952
- router.events.off("routeChangeComplete", change_complete);
953
- router.events.off("routeChangeError", change_error);
954
- };
955
- }, [router]);
1093
+ }
1094
+
1095
+ // Equivalent to routeChangeStart - runs when navigation begins
1096
+ const main_state_dispatch = dispatch["reflex___state____state"];
1097
+ if (main_state_dispatch !== undefined) {
1098
+ main_state_dispatch({ is_hydrated_rx_state_: false });
1099
+ }
1100
+
1101
+ // Equivalent to routeChangeComplete - runs after navigation completes
1102
+ addEvents(onLoadInternalEvent());
1103
+
1104
+ // Update the ref
1105
+ prevLocationRef.current = location;
1106
+ }, [location, dispatch, onLoadInternalEvent, addEvents]);
956
1107
 
957
1108
  return [addEvents, connectErrors];
958
1109
  };