reflex 0.7.14a6__py3-none-any.whl → 0.8.0a1__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 (206) 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 +16 -10
  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/utils/context.js.jinja2 +25 -6
  7. reflex/.templates/web/app/entry.client.js +8 -0
  8. reflex/.templates/web/app/routes.js +10 -0
  9. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -32
  10. reflex/.templates/web/postcss.config.js +1 -1
  11. reflex/.templates/web/react-router.config.js +6 -0
  12. reflex/.templates/web/utils/client_side_routing.js +21 -19
  13. reflex/.templates/web/utils/react-theme.js +89 -0
  14. reflex/.templates/web/utils/state.js +155 -67
  15. reflex/.templates/web/vite.config.js +32 -0
  16. reflex/__init__.py +1 -6
  17. reflex/__init__.pyi +0 -4
  18. reflex/app.py +52 -115
  19. reflex/base.py +1 -87
  20. reflex/compiler/compiler.py +40 -3
  21. reflex/compiler/utils.py +54 -28
  22. reflex/components/__init__.py +0 -2
  23. reflex/components/__init__.pyi +0 -3
  24. reflex/components/base/__init__.py +1 -5
  25. reflex/components/base/__init__.pyi +4 -6
  26. reflex/components/base/app_wrap.pyi +5 -4
  27. reflex/components/base/body.pyi +5 -4
  28. reflex/components/base/document.py +18 -14
  29. reflex/components/base/document.pyi +83 -27
  30. reflex/components/base/error_boundary.pyi +5 -4
  31. reflex/components/base/fragment.pyi +5 -4
  32. reflex/components/base/link.pyi +9 -7
  33. reflex/components/base/meta.pyi +17 -13
  34. reflex/components/base/script.py +60 -58
  35. reflex/components/base/script.pyi +246 -31
  36. reflex/components/base/strict_mode.pyi +5 -4
  37. reflex/components/component.py +109 -194
  38. reflex/components/core/__init__.py +1 -0
  39. reflex/components/core/__init__.pyi +1 -0
  40. reflex/components/core/auto_scroll.pyi +5 -4
  41. reflex/components/core/banner.pyi +25 -19
  42. reflex/components/core/client_side_routing.py +7 -6
  43. reflex/components/core/client_side_routing.pyi +6 -56
  44. reflex/components/core/clipboard.pyi +5 -4
  45. reflex/components/core/debounce.py +1 -0
  46. reflex/components/core/debounce.pyi +5 -4
  47. reflex/components/core/foreach.py +3 -2
  48. reflex/components/core/helmet.py +14 -0
  49. reflex/components/{next/base.pyi → core/helmet.pyi} +10 -7
  50. reflex/components/core/html.pyi +5 -4
  51. reflex/components/core/sticky.pyi +17 -13
  52. reflex/components/core/upload.py +2 -1
  53. reflex/components/core/upload.pyi +21 -16
  54. reflex/components/datadisplay/code.pyi +9 -7
  55. reflex/components/datadisplay/dataeditor.pyi +5 -4
  56. reflex/components/datadisplay/shiki_code_block.pyi +13 -10
  57. reflex/components/dynamic.py +4 -5
  58. reflex/components/el/element.pyi +5 -4
  59. reflex/components/el/elements/base.pyi +5 -4
  60. reflex/components/el/elements/forms.pyi +69 -52
  61. reflex/components/el/elements/inline.pyi +113 -85
  62. reflex/components/el/elements/media.pyi +105 -79
  63. reflex/components/el/elements/metadata.pyi +25 -19
  64. reflex/components/el/elements/other.pyi +29 -22
  65. reflex/components/el/elements/scripts.pyi +13 -10
  66. reflex/components/el/elements/sectioning.pyi +61 -46
  67. reflex/components/el/elements/tables.pyi +41 -31
  68. reflex/components/el/elements/typography.pyi +61 -46
  69. reflex/components/field.py +175 -0
  70. reflex/components/gridjs/datatable.py +2 -2
  71. reflex/components/gridjs/datatable.pyi +11 -9
  72. reflex/components/lucide/icon.py +6 -2
  73. reflex/components/lucide/icon.pyi +15 -10
  74. reflex/components/markdown/markdown.pyi +5 -4
  75. reflex/components/moment/moment.pyi +5 -4
  76. reflex/components/plotly/plotly.pyi +19 -10
  77. reflex/components/props.py +376 -27
  78. reflex/components/radix/primitives/accordion.py +8 -1
  79. reflex/components/radix/primitives/accordion.pyi +29 -22
  80. reflex/components/radix/primitives/base.pyi +9 -7
  81. reflex/components/radix/primitives/drawer.pyi +45 -34
  82. reflex/components/radix/primitives/form.pyi +41 -31
  83. reflex/components/radix/primitives/progress.pyi +21 -16
  84. reflex/components/radix/primitives/slider.pyi +21 -16
  85. reflex/components/radix/themes/base.py +3 -3
  86. reflex/components/radix/themes/base.pyi +33 -25
  87. reflex/components/radix/themes/color_mode.pyi +13 -10
  88. reflex/components/radix/themes/components/alert_dialog.pyi +29 -22
  89. reflex/components/radix/themes/components/aspect_ratio.pyi +5 -4
  90. reflex/components/radix/themes/components/avatar.pyi +5 -4
  91. reflex/components/radix/themes/components/badge.pyi +5 -4
  92. reflex/components/radix/themes/components/button.pyi +5 -4
  93. reflex/components/radix/themes/components/callout.pyi +21 -16
  94. reflex/components/radix/themes/components/card.pyi +5 -4
  95. reflex/components/radix/themes/components/checkbox.pyi +13 -10
  96. reflex/components/radix/themes/components/checkbox_cards.pyi +9 -7
  97. reflex/components/radix/themes/components/checkbox_group.pyi +9 -7
  98. reflex/components/radix/themes/components/context_menu.pyi +53 -40
  99. reflex/components/radix/themes/components/data_list.pyi +17 -13
  100. reflex/components/radix/themes/components/dialog.pyi +29 -22
  101. reflex/components/radix/themes/components/dropdown_menu.pyi +33 -25
  102. reflex/components/radix/themes/components/hover_card.pyi +17 -13
  103. reflex/components/radix/themes/components/icon_button.pyi +5 -4
  104. reflex/components/radix/themes/components/inset.pyi +5 -4
  105. reflex/components/radix/themes/components/popover.pyi +17 -13
  106. reflex/components/radix/themes/components/progress.pyi +5 -4
  107. reflex/components/radix/themes/components/radio.pyi +5 -4
  108. reflex/components/radix/themes/components/radio_cards.pyi +9 -7
  109. reflex/components/radix/themes/components/radio_group.pyi +17 -13
  110. reflex/components/radix/themes/components/scroll_area.pyi +5 -4
  111. reflex/components/radix/themes/components/segmented_control.pyi +9 -7
  112. reflex/components/radix/themes/components/select.pyi +37 -28
  113. reflex/components/radix/themes/components/separator.pyi +5 -4
  114. reflex/components/radix/themes/components/skeleton.pyi +5 -4
  115. reflex/components/radix/themes/components/slider.pyi +5 -4
  116. reflex/components/radix/themes/components/spinner.pyi +5 -4
  117. reflex/components/radix/themes/components/switch.pyi +5 -4
  118. reflex/components/radix/themes/components/table.pyi +29 -22
  119. reflex/components/radix/themes/components/tabs.pyi +21 -16
  120. reflex/components/radix/themes/components/text_area.pyi +5 -4
  121. reflex/components/radix/themes/components/text_field.pyi +13 -10
  122. reflex/components/radix/themes/components/tooltip.pyi +5 -4
  123. reflex/components/radix/themes/layout/base.pyi +5 -4
  124. reflex/components/radix/themes/layout/box.pyi +5 -4
  125. reflex/components/radix/themes/layout/center.pyi +5 -4
  126. reflex/components/radix/themes/layout/container.pyi +5 -4
  127. reflex/components/radix/themes/layout/flex.pyi +5 -4
  128. reflex/components/radix/themes/layout/grid.pyi +5 -4
  129. reflex/components/radix/themes/layout/list.pyi +21 -16
  130. reflex/components/radix/themes/layout/section.pyi +5 -4
  131. reflex/components/radix/themes/layout/spacer.pyi +5 -4
  132. reflex/components/radix/themes/layout/stack.pyi +13 -10
  133. reflex/components/radix/themes/typography/blockquote.pyi +5 -4
  134. reflex/components/radix/themes/typography/code.pyi +5 -4
  135. reflex/components/radix/themes/typography/heading.pyi +5 -4
  136. reflex/components/radix/themes/typography/link.py +42 -9
  137. reflex/components/radix/themes/typography/link.pyi +311 -6
  138. reflex/components/radix/themes/typography/text.pyi +29 -22
  139. reflex/components/react_player/audio.pyi +5 -4
  140. reflex/components/react_player/react_player.pyi +5 -4
  141. reflex/components/react_player/video.pyi +5 -4
  142. reflex/components/recharts/cartesian.py +2 -1
  143. reflex/components/recharts/cartesian.pyi +65 -46
  144. reflex/components/recharts/charts.py +4 -2
  145. reflex/components/recharts/charts.pyi +36 -24
  146. reflex/components/recharts/general.pyi +24 -18
  147. reflex/components/recharts/polar.py +8 -4
  148. reflex/components/recharts/polar.pyi +16 -10
  149. reflex/components/recharts/recharts.pyi +9 -7
  150. reflex/components/sonner/toast.py +2 -2
  151. reflex/components/sonner/toast.pyi +7 -6
  152. reflex/config.py +3 -77
  153. reflex/constants/__init__.py +1 -0
  154. reflex/constants/base.py +38 -8
  155. reflex/constants/compiler.py +4 -2
  156. reflex/constants/event.py +1 -0
  157. reflex/constants/installer.py +23 -16
  158. reflex/constants/state.py +2 -0
  159. reflex/custom_components/custom_components.py +0 -14
  160. reflex/environment.py +1 -1
  161. reflex/event.py +178 -81
  162. reflex/experimental/__init__.py +0 -30
  163. reflex/istate/proxy.py +5 -3
  164. reflex/page.py +0 -27
  165. reflex/plugins/__init__.py +3 -2
  166. reflex/plugins/base.py +5 -1
  167. reflex/plugins/shared_tailwind.py +158 -0
  168. reflex/plugins/sitemap.py +206 -0
  169. reflex/plugins/tailwind_v3.py +13 -106
  170. reflex/plugins/tailwind_v4.py +15 -108
  171. reflex/reflex.py +1 -0
  172. reflex/state.py +134 -140
  173. reflex/testing.py +57 -9
  174. reflex/utils/build.py +9 -69
  175. reflex/utils/exec.py +59 -161
  176. reflex/utils/export.py +1 -1
  177. reflex/utils/imports.py +0 -4
  178. reflex/utils/misc.py +28 -0
  179. reflex/utils/prerequisites.py +62 -59
  180. reflex/utils/processes.py +6 -6
  181. reflex/utils/pyi_generator.py +21 -9
  182. reflex/utils/serializers.py +14 -1
  183. reflex/utils/types.py +196 -61
  184. reflex/vars/__init__.py +2 -0
  185. reflex/vars/base.py +367 -134
  186. {reflex-0.7.14a6.dist-info → reflex-0.8.0a1.dist-info}/METADATA +12 -5
  187. {reflex-0.7.14a6.dist-info → reflex-0.8.0a1.dist-info}/RECORD +190 -197
  188. reflex/.templates/web/next.config.js +0 -7
  189. reflex/components/base/head.py +0 -20
  190. reflex/components/base/head.pyi +0 -116
  191. reflex/components/next/__init__.py +0 -10
  192. reflex/components/next/base.py +0 -7
  193. reflex/components/next/image.py +0 -117
  194. reflex/components/next/image.pyi +0 -94
  195. reflex/components/next/link.py +0 -20
  196. reflex/components/next/link.pyi +0 -67
  197. reflex/components/next/video.py +0 -38
  198. reflex/components/next/video.pyi +0 -68
  199. reflex/components/suneditor/__init__.py +0 -5
  200. reflex/components/suneditor/editor.py +0 -269
  201. reflex/components/suneditor/editor.pyi +0 -199
  202. reflex/experimental/layout.py +0 -254
  203. reflex/experimental/layout.pyi +0 -814
  204. {reflex-0.7.14a6.dist-info → reflex-0.8.0a1.dist-info}/WHEEL +0 -0
  205. {reflex-0.7.14a6.dist-info → reflex-0.8.0a1.dist-info}/entry_points.txt +0 -0
  206. {reflex-0.7.14a6.dist-info → reflex-0.8.0a1.dist-info}/licenses/LICENSE +0 -0
reflex/app.py CHANGED
@@ -13,7 +13,7 @@ import io
13
13
  import json
14
14
  import sys
15
15
  import traceback
16
- from collections.abc import AsyncIterator, Callable, Coroutine, Sequence
16
+ from collections.abc import AsyncIterator, Callable, Coroutine, Mapping, Sequence
17
17
  from datetime import datetime
18
18
  from pathlib import Path
19
19
  from timeit import default_timer as timer
@@ -32,7 +32,6 @@ from starlette.middleware import cors
32
32
  from starlette.requests import ClientDisconnect, Request
33
33
  from starlette.responses import JSONResponse, Response, StreamingResponse
34
34
  from starlette.staticfiles import StaticFiles
35
- from typing_extensions import deprecated
36
35
 
37
36
  from reflex import constants
38
37
  from reflex.admin import AdminDash
@@ -61,7 +60,7 @@ from reflex.components.core.banner import (
61
60
  )
62
61
  from reflex.components.core.breakpoints import set_breakpoints
63
62
  from reflex.components.core.client_side_routing import (
64
- Default404Page,
63
+ default_404_page,
65
64
  wait_for_client_redirect,
66
65
  )
67
66
  from reflex.components.core.sticky import sticky
@@ -246,8 +245,6 @@ class UploadFile(StarletteUploadFile):
246
245
 
247
246
  path: Path | None = dataclasses.field(default=None)
248
247
 
249
- _deprecated_filename: str | None = dataclasses.field(default=None)
250
-
251
248
  size: int | None = dataclasses.field(default=None)
252
249
 
253
250
  headers: Headers = dataclasses.field(default_factory=Headers)
@@ -263,21 +260,6 @@ class UploadFile(StarletteUploadFile):
263
260
  return self.path.name
264
261
  return None
265
262
 
266
- @property
267
- def filename(self) -> str | None:
268
- """Get the filename of the uploaded file.
269
-
270
- Returns:
271
- The filename of the uploaded file.
272
- """
273
- console.deprecate(
274
- feature_name="UploadFile.filename",
275
- reason="Use UploadFile.name instead.",
276
- deprecation_version="0.7.1",
277
- removal_version="0.8.0",
278
- )
279
- return self._deprecated_filename
280
-
281
263
 
282
264
  @dataclasses.dataclass(
283
265
  frozen=True,
@@ -291,8 +273,8 @@ class UnevaluatedPage:
291
273
  description: Var | str | None
292
274
  image: str
293
275
  on_load: EventType[()] | None
294
- meta: list[dict[str, str]]
295
- context: dict[str, Any] | None
276
+ meta: Sequence[Mapping[str, str]]
277
+ context: Mapping[str, Any]
296
278
 
297
279
  def merged_with(self, other: UnevaluatedPage) -> UnevaluatedPage:
298
280
  """Merge the other page into this one.
@@ -355,9 +337,6 @@ class App(MiddlewareMixin, LifespanMixin):
355
337
  default=None
356
338
  )
357
339
 
358
- # Error boundary component to wrap the app with.
359
- error_boundary: ComponentCallable | None = dataclasses.field(default=None)
360
-
361
340
  # App wraps to be applied to the whole app. Expected to be a dictionary of (order, name) to a function that takes whether the state is enabled and optionally returns a component.
362
341
  app_wraps: dict[tuple[int, str], Callable[[bool], Component | None]] = (
363
342
  dataclasses.field(
@@ -443,24 +422,6 @@ class App(MiddlewareMixin, LifespanMixin):
443
422
  # FastAPI app for compatibility with FastAPI.
444
423
  _cached_fastapi_app: FastAPI | None = None
445
424
 
446
- @property
447
- @deprecated("Use `api_transformer=your_fastapi_app` instead.")
448
- def api(self) -> FastAPI:
449
- """Get the backend api.
450
-
451
- Returns:
452
- The backend api.
453
- """
454
- if self._cached_fastapi_app is None:
455
- self._cached_fastapi_app = FastAPI()
456
- console.deprecate(
457
- feature_name="App.api",
458
- reason="Set `api_transformer=your_fastapi_app` instead.",
459
- deprecation_version="0.7.9",
460
- removal_version="0.8.0",
461
- )
462
- return self._cached_fastapi_app
463
-
464
425
  @property
465
426
  def event_namespace(self) -> EventNamespace | None:
466
427
  """Get the event namespace.
@@ -619,7 +580,7 @@ class App(MiddlewareMixin, LifespanMixin):
619
580
  self._apply_decorated_pages()
620
581
 
621
582
  compile_future = concurrent.futures.ThreadPoolExecutor(max_workers=1).submit(
622
- self._compile
583
+ self._compile, prerender_routes=is_prod_mode()
623
584
  )
624
585
 
625
586
  def callback(f: concurrent.futures.Future):
@@ -800,7 +761,7 @@ class App(MiddlewareMixin, LifespanMixin):
800
761
 
801
762
  if route == constants.Page404.SLUG:
802
763
  if component is None:
803
- component = Default404Page.create()
764
+ component = default_404_page
804
765
  component = wait_for_client_redirect(self._generate_component(component))
805
766
  title = title or constants.Page404.TITLE
806
767
  description = description or constants.Page404.DESCRIPTION
@@ -821,7 +782,7 @@ class App(MiddlewareMixin, LifespanMixin):
821
782
  image=image,
822
783
  on_load=on_load,
823
784
  meta=meta,
824
- context=context,
785
+ context=context or {},
825
786
  )
826
787
 
827
788
  if route in self._unevaluated_pages:
@@ -892,10 +853,39 @@ class App(MiddlewareMixin, LifespanMixin):
892
853
  Returns:
893
854
  The load events for the route.
894
855
  """
895
- route = route.lstrip("/")
856
+ route = route.lstrip("/").rstrip("/")
896
857
  if route == "":
897
- route = constants.PageNames.INDEX_ROUTE
898
- return self._load_events.get(route, [])
858
+ return self._load_events.get(constants.PageNames.INDEX_ROUTE, [])
859
+
860
+ # Separate the pages by route type.
861
+ static_page_paths_to_page_route = {}
862
+ dynamic_page_paths_to_page_route = {}
863
+ for page_route in list(self._pages) + list(self._unevaluated_pages):
864
+ page_path = page_route.lstrip("/").rstrip("/")
865
+ if "[" in page_path and "]" in page_path:
866
+ dynamic_page_paths_to_page_route[page_path] = page_route
867
+ else:
868
+ static_page_paths_to_page_route[page_path] = page_route
869
+
870
+ # Check for static routes.
871
+ if (page_route := static_page_paths_to_page_route.get(route)) is not None:
872
+ return self._load_events.get(page_route, [])
873
+
874
+ # Check for dynamic routes.
875
+ parts = route.split("/")
876
+ for page_path, page_route in dynamic_page_paths_to_page_route.items():
877
+ page_parts = page_path.split("/")
878
+ if len(page_parts) != len(parts):
879
+ continue
880
+ if all(
881
+ part == page_part
882
+ or (page_part.startswith("[") and page_part.endswith("]"))
883
+ for part, page_part in zip(parts, page_parts, strict=False)
884
+ ):
885
+ return self._load_events.get(page_route, [])
886
+
887
+ # Default to 404 page load events if no match found.
888
+ return self._load_events.get("404", [])
899
889
 
900
890
  def _check_routes_conflict(self, new_route: str):
901
891
  """Verify if there is any conflict between the new route and any existing route.
@@ -938,44 +928,6 @@ class App(MiddlewareMixin, LifespanMixin):
938
928
  # info1 will break away into its own tree.
939
929
  break
940
930
 
941
- def add_custom_404_page(
942
- self,
943
- component: Component | ComponentCallable | None = None,
944
- title: str = constants.Page404.TITLE,
945
- image: str = constants.Page404.IMAGE,
946
- description: str = constants.Page404.DESCRIPTION,
947
- on_load: EventType[()] | None = None,
948
- meta: list[dict[str, str]] = constants.DefaultPage.META_LIST,
949
- ):
950
- """Define a custom 404 page for any url having no match.
951
-
952
- If there is no page defined on 'index' route, add the 404 page to it.
953
- If there is no global catchall defined, add the 404 page with a catchall.
954
-
955
- Args:
956
- component: The component to display at the page.
957
- title: The title of the page.
958
- image: The image to display on the page.
959
- description: The description of the page.
960
- on_load: The event handler(s) that will be called each time the page load.
961
- meta: The metadata of the page.
962
- """
963
- console.deprecate(
964
- feature_name="App.add_custom_404_page",
965
- reason=f"Use app.add_page(component, route='/{constants.Page404.SLUG}') instead.",
966
- deprecation_version="0.6.7",
967
- removal_version="0.8.0",
968
- )
969
- self.add_page(
970
- component=component,
971
- route=constants.Page404.SLUG,
972
- title=title or constants.Page404.TITLE,
973
- image=image or constants.Page404.IMAGE,
974
- description=description or constants.Page404.DESCRIPTION,
975
- on_load=on_load,
976
- meta=meta,
977
- )
978
-
979
931
  def _setup_admin_dash(self):
980
932
  """Setup the admin dash."""
981
933
  try:
@@ -1024,7 +976,7 @@ class App(MiddlewareMixin, LifespanMixin):
1024
976
  for i, tags in imports.items()
1025
977
  if i not in dependencies
1026
978
  and i not in dev_dependencies
1027
- and not any(i.startswith(prefix) for prefix in ["/", "$/", ".", "next/"])
979
+ and not any(i.startswith(prefix) for prefix in ["/", "$/", "."])
1028
980
  and i != ""
1029
981
  and any(tag.install for tag in tags)
1030
982
  }
@@ -1148,17 +1100,17 @@ class App(MiddlewareMixin, LifespanMixin):
1148
1100
  )
1149
1101
  for dep in dep_set:
1150
1102
  if dep not in state_cls.vars and dep not in state_cls.backend_vars:
1151
- msg = f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {state_name}.{dep}"
1103
+ msg = f"ComputedVar {var._name} on state {state.__name__} has an invalid dependency {state_name}.{dep}"
1152
1104
  raise exceptions.VarDependencyError(msg)
1153
1105
 
1154
1106
  for substate in state.class_subclasses:
1155
1107
  self._validate_var_dependencies(substate)
1156
1108
 
1157
- def _compile(self, export: bool = False, dry_run: bool = False):
1109
+ def _compile(self, prerender_routes: bool = False, dry_run: bool = False):
1158
1110
  """Compile the app and output it to the pages folder.
1159
1111
 
1160
1112
  Args:
1161
- export: Whether to compile the app for export.
1113
+ prerender_routes: Whether to prerender the routes.
1162
1114
  dry_run: Whether to compile the app without saving it.
1163
1115
 
1164
1116
  Raises:
@@ -1329,17 +1281,6 @@ class App(MiddlewareMixin, LifespanMixin):
1329
1281
  # Add the app wrappers from this component.
1330
1282
  app_wrappers.update(component._get_all_app_wrap_components())
1331
1283
 
1332
- if self.error_boundary:
1333
- from reflex.compiler.compiler import into_component
1334
-
1335
- console.deprecate(
1336
- feature_name="App.error_boundary",
1337
- reason="Use app_wraps instead.",
1338
- deprecation_version="0.7.1",
1339
- removal_version="0.8.0",
1340
- )
1341
- app_wrappers[(55, "ErrorBoundary")] = into_component(self.error_boundary)
1342
-
1343
1284
  # Perform auto-memoization of stateful components.
1344
1285
  with console.timing("Auto-memoize StatefulComponents"):
1345
1286
  (
@@ -1441,6 +1382,7 @@ class App(MiddlewareMixin, LifespanMixin):
1441
1382
  )
1442
1383
  )
1443
1384
  ),
1385
+ unevaluated_pages=list(self._unevaluated_pages.values()),
1444
1386
  )
1445
1387
 
1446
1388
  # Wait for all compilation tasks to complete.
@@ -1484,15 +1426,9 @@ class App(MiddlewareMixin, LifespanMixin):
1484
1426
  with console.timing("Install Frontend Packages"):
1485
1427
  self._get_frontend_packages(all_imports)
1486
1428
 
1487
- # Setup the next.config.js
1488
- transpile_packages = [
1489
- package
1490
- for package, import_vars in all_imports.items()
1491
- if any(import_var.transpile for import_var in import_vars)
1492
- ]
1493
- prerequisites.update_next_config(
1494
- export=export,
1495
- transpile_packages=transpile_packages,
1429
+ # Setup the react-router.config.js
1430
+ prerequisites.update_react_router_config(
1431
+ prerender_routes=prerender_routes,
1496
1432
  )
1497
1433
 
1498
1434
  if is_prod_mode():
@@ -1501,9 +1437,11 @@ class App(MiddlewareMixin, LifespanMixin):
1501
1437
  else:
1502
1438
  # In dev mode, delete removed pages and update existing pages.
1503
1439
  keep_files = [Path(output_path) for output_path, _ in compile_results]
1504
- for p in Path(prerequisites.get_web_dir() / constants.Dirs.PAGES).rglob(
1505
- "*"
1506
- ):
1440
+ for p in Path(
1441
+ prerequisites.get_web_dir()
1442
+ / constants.Dirs.PAGES
1443
+ / constants.Dirs.ROUTES
1444
+ ).rglob("*"):
1507
1445
  if p.is_file() and p not in keep_files:
1508
1446
  # Remove pages that are no longer in the app.
1509
1447
  p.unlink()
@@ -1970,7 +1908,6 @@ def upload(app: App):
1970
1908
  UploadFile(
1971
1909
  file=content_copy,
1972
1910
  path=Path(file.filename.lstrip("/")) if file.filename else None,
1973
- _deprecated_filename=file.filename,
1974
1911
  size=file.size,
1975
1912
  headers=file.headers,
1976
1913
  )
reflex/base.py CHANGED
@@ -1,48 +1,6 @@
1
1
  """Define the base Reflex class."""
2
2
 
3
- from __future__ import annotations
4
-
5
- import os
6
- from typing import TYPE_CHECKING, Any
7
-
8
- import pydantic.v1.main as pydantic_main
9
3
  from pydantic.v1 import BaseModel
10
- from pydantic.v1.fields import ModelField
11
-
12
-
13
- def validate_field_name(bases: list[type[BaseModel]], field_name: str) -> None:
14
- """Ensure that the field's name does not shadow an existing attribute of the model.
15
-
16
- Args:
17
- bases: List of base models to check for shadowed attrs.
18
- field_name: name of attribute
19
-
20
- Raises:
21
- VarNameError: If state var field shadows another in its parent state
22
- """
23
- from reflex.utils.exceptions import VarNameError
24
-
25
- # can't use reflex.config.environment here cause of circular import
26
- reload = os.getenv("__RELOAD_CONFIG", "").lower() == "true"
27
- base = None
28
- try:
29
- for base in bases:
30
- if not reload and getattr(base, field_name, None):
31
- pass
32
- except TypeError as te:
33
- msg = (
34
- f'State var "{field_name}" in {base} has been shadowed by a substate var; '
35
- f'use a different field name instead".'
36
- )
37
- raise VarNameError(msg) from te
38
-
39
-
40
- # monkeypatch pydantic validate_field_name method to skip validating
41
- # shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
42
- pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPrivateImportUsage]
43
-
44
- if TYPE_CHECKING:
45
- from reflex.vars import Var
46
4
 
47
5
 
48
6
  class Base(BaseModel):
@@ -75,7 +33,7 @@ class Base(BaseModel):
75
33
  default=serialize,
76
34
  )
77
35
 
78
- def set(self, **kwargs: Any):
36
+ def set(self, **kwargs: object):
79
37
  """Set multiple fields and return the object.
80
38
 
81
39
  Args:
@@ -87,47 +45,3 @@ class Base(BaseModel):
87
45
  for key, value in kwargs.items():
88
46
  setattr(self, key, value)
89
47
  return self
90
-
91
- @classmethod
92
- def get_fields(cls) -> dict[str, ModelField]:
93
- """Get the fields of the object.
94
-
95
- Returns:
96
- The fields of the object.
97
- """
98
- return cls.__fields__
99
-
100
- @classmethod
101
- def add_field(cls, var: Var, default_value: Any):
102
- """Add a pydantic field after class definition.
103
-
104
- Used by State.add_var() to correctly handle the new variable.
105
-
106
- Args:
107
- var: The variable to add a pydantic field for.
108
- default_value: The default value of the field
109
- """
110
- var_name = var._var_field_name
111
- new_field = ModelField.infer(
112
- name=var_name,
113
- value=default_value,
114
- annotation=var._var_type,
115
- class_validators=None,
116
- config=cls.__config__,
117
- )
118
- cls.__fields__.update({var_name: new_field})
119
-
120
- def get_value(self, key: str) -> Any:
121
- """Get the value of a field.
122
-
123
- Args:
124
- key: The key of the field.
125
-
126
- Returns:
127
- The value of the field.
128
- """
129
- if isinstance(key, str):
130
- # Seems like this function signature was wrong all along?
131
- # If the user wants a field that we know of, get it and pass it off to _get_value
132
- return getattr(self, key)
133
- return key
@@ -21,12 +21,14 @@ from reflex.components.component import (
21
21
  )
22
22
  from reflex.config import get_config
23
23
  from reflex.constants.compiler import PageNames
24
+ from reflex.constants.state import FIELD_MARKER
24
25
  from reflex.environment import environment
25
26
  from reflex.state import BaseState
26
27
  from reflex.style import SYSTEM_COLOR_MODE
27
28
  from reflex.utils import console, path_ops
28
29
  from reflex.utils.exceptions import ReflexError
29
30
  from reflex.utils.exec import is_prod_mode
31
+ from reflex.utils.format import to_title_case
30
32
  from reflex.utils.imports import ImportVar
31
33
  from reflex.utils.prerequisites import get_web_dir
32
34
  from reflex.vars.base import LiteralVar, Var
@@ -469,7 +471,9 @@ def compile_document_root(
469
471
  The path and code of the compiled document root.
470
472
  """
471
473
  # Get the path for the output file.
472
- output_path = utils.get_page_path(constants.PageNames.DOCUMENT_ROOT)
474
+ output_path = str(
475
+ get_web_dir() / constants.Dirs.PAGES / constants.PageNames.DOCUMENT_ROOT
476
+ )
473
477
 
474
478
  # Create the document root.
475
479
  document_root = utils.create_document_root(
@@ -491,7 +495,9 @@ def compile_app(app_root: Component) -> tuple[str, str]:
491
495
  The path and code of the compiled app wrapper.
492
496
  """
493
497
  # Get the path for the output file.
494
- output_path = utils.get_page_path(constants.PageNames.APP_ROOT)
498
+ output_path = str(
499
+ get_web_dir() / constants.Dirs.PAGES / constants.PageNames.APP_ROOT
500
+ )
495
501
 
496
502
  # Compile the document root.
497
503
  code = _compile_app(app_root)
@@ -606,7 +612,7 @@ def purge_web_pages_dir():
606
612
  return
607
613
 
608
614
  # Empty out the web pages directory.
609
- utils.empty_dir(get_web_dir() / constants.Dirs.PAGES, keep_files=["_app.js"])
615
+ utils.empty_dir(get_web_dir() / constants.Dirs.PAGES, keep_files=["routes.js"])
610
616
 
611
617
 
612
618
  if TYPE_CHECKING:
@@ -668,6 +674,32 @@ def readable_name_from_component(
668
674
  return None
669
675
 
670
676
 
677
+ def _modify_exception(e: Exception) -> None:
678
+ """Modify the exception to make it more readable.
679
+
680
+ Args:
681
+ e: The exception to modify.
682
+ """
683
+ if len(e.args) == 1 and isinstance((msg := e.args[0]), str):
684
+ while (state_index := msg.find("reflex___")) != -1:
685
+ dot_index = msg.find(".", state_index)
686
+ if dot_index == -1:
687
+ break
688
+ state_name = msg[state_index:dot_index]
689
+ module_dot_state_name = state_name.replace("___", ".").rsplit("__", 1)[-1]
690
+ module_path, _, state_snake_case = module_dot_state_name.rpartition(".")
691
+ if not state_snake_case:
692
+ break
693
+ actual_state_name = to_title_case(state_snake_case)
694
+ msg = (
695
+ f"{msg[:state_index]}{module_path}.{actual_state_name}{msg[dot_index:]}"
696
+ )
697
+
698
+ msg = msg.replace(FIELD_MARKER, "")
699
+
700
+ e.args = (msg,)
701
+
702
+
671
703
  def into_component(component: Component | ComponentCallable) -> Component:
672
704
  """Convert a component to a Component.
673
705
 
@@ -692,6 +724,7 @@ def into_component(component: Component | ComponentCallable) -> Component:
692
724
  component_called = component()
693
725
  except KeyError as e:
694
726
  if isinstance(e, ReflexError):
727
+ _modify_exception(e)
695
728
  raise
696
729
  key = e.args[0] if e.args else None
697
730
  if key is not None and isinstance(key, Var):
@@ -701,6 +734,7 @@ def into_component(component: Component | ComponentCallable) -> Component:
701
734
  raise
702
735
  except TypeError as e:
703
736
  if isinstance(e, ReflexError):
737
+ _modify_exception(e)
704
738
  raise
705
739
  message = e.args[0] if e.args else None
706
740
  if message and isinstance(message, str):
@@ -726,6 +760,9 @@ def into_component(component: Component | ComponentCallable) -> Component:
726
760
  "Cannot pass a Var to a built-in function. Consider moving the operation to the backend, using existing Var operations, or defining a custom Var operation."
727
761
  ).with_traceback(e.__traceback__) from None
728
762
  raise
763
+ except ReflexError as e:
764
+ _modify_exception(e)
765
+ raise
729
766
 
730
767
  if (converted := _into_component_once(component_called)) is not None:
731
768
  return converted
reflex/compiler/utils.py CHANGED
@@ -11,22 +11,15 @@ from pathlib import Path
11
11
  from typing import Any
12
12
  from urllib.parse import urlparse
13
13
 
14
- from pydantic.v1.fields import ModelField
15
-
16
14
  from reflex import constants
17
- from reflex.components.base import (
18
- Body,
19
- Description,
20
- DocumentHead,
21
- Head,
22
- Html,
23
- Image,
24
- Main,
25
- Meta,
26
- NextScript,
27
- Title,
28
- )
15
+ from reflex.components.base import Description, Image, Scripts
16
+ from reflex.components.base.document import Links, ScrollRestoration
17
+ from reflex.components.base.document import Meta as ReactMeta
29
18
  from reflex.components.component import Component, ComponentStyle, CustomComponent
19
+ from reflex.components.el.elements.metadata import Head, Meta, Title
20
+ from reflex.components.el.elements.other import Html
21
+ from reflex.components.el.elements.sectioning import Body
22
+ from reflex.constants.state import FIELD_MARKER
30
23
  from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
31
24
  from reflex.state import BaseState, _resolve_delta
32
25
  from reflex.style import Style
@@ -34,7 +27,7 @@ from reflex.utils import console, format, imports, path_ops
34
27
  from reflex.utils.exec import is_in_app_harness
35
28
  from reflex.utils.imports import ImportVar, ParsedImportDict
36
29
  from reflex.utils.prerequisites import get_web_dir
37
- from reflex.vars.base import Var
30
+ from reflex.vars.base import Field, Var
38
31
 
39
32
  # To re-export this function.
40
33
  merge_imports = imports.merge_imports
@@ -212,7 +205,7 @@ def compile_state(state: type[BaseState]) -> dict:
212
205
 
213
206
 
214
207
  def _compile_client_storage_field(
215
- field: ModelField,
208
+ field: Field,
216
209
  ) -> tuple[
217
210
  type[Cookie] | type[LocalStorage] | type[SessionStorage] | None,
218
211
  dict[str, Any] | None,
@@ -260,7 +253,7 @@ def _compile_client_storage_recursive(
260
253
  if name in state.inherited_vars:
261
254
  # only include vars defined in this state
262
255
  continue
263
- state_key = f"{state_name}.{name}"
256
+ state_key = f"{state_name}.{name}" + FIELD_MARKER
264
257
  field_type, options = _compile_client_storage_field(field)
265
258
  if field_type is Cookie:
266
259
  cookies[state_key] = options
@@ -320,6 +313,8 @@ def compile_custom_component(
320
313
  if lib != component.library
321
314
  }
322
315
 
316
+ imports.setdefault("@emotion/react", []).append(ImportVar("jsx"))
317
+
323
318
  # Concatenate the props.
324
319
  props = list(component.props)
325
320
 
@@ -352,12 +347,27 @@ def create_document_root(
352
347
  Returns:
353
348
  The document root.
354
349
  """
355
- head_components = head_components or []
350
+ head_components = [
351
+ *(
352
+ head_components
353
+ or [
354
+ # Default meta tags if user does not provide.
355
+ Meta.create(char_set="utf-8"),
356
+ Meta.create(
357
+ name="viewport", content="width=device-width, initial-scale=1"
358
+ ),
359
+ ]
360
+ ),
361
+ # Always include the framework meta and link tags.
362
+ ReactMeta.create(),
363
+ Links.create(),
364
+ ]
356
365
  return Html.create(
357
- DocumentHead.create(*head_components),
366
+ Head.create(*head_components),
358
367
  Body.create(
359
- Main.create(),
360
- NextScript.create(),
368
+ Var("children"),
369
+ ScrollRestoration.create(),
370
+ Scripts.create(),
361
371
  ),
362
372
  lang=html_lang or "en",
363
373
  custom_attrs=html_custom_attrs or {},
@@ -391,6 +401,21 @@ def create_theme(style: ComponentStyle) -> dict:
391
401
  return {"styles": {"global": root_style}}
392
402
 
393
403
 
404
+ def _format_route_part(part: str) -> str:
405
+ if part.startswith("[") and part.endswith("]"):
406
+ return f"${part}_"
407
+ return "[" + part + "]_"
408
+
409
+
410
+ def _path_to_file_stem(path: str) -> str:
411
+ if path == "index":
412
+ return "_index"
413
+ path = path if path != "index" else "/"
414
+ return (
415
+ ".".join([_format_route_part(part) for part in path.split("/")]) + "._index"
416
+ ).lstrip(".")
417
+
418
+
394
419
  def get_page_path(path: str) -> str:
395
420
  """Get the path of the compiled JS file for the given page.
396
421
 
@@ -400,7 +425,12 @@ def get_page_path(path: str) -> str:
400
425
  Returns:
401
426
  The path of the compiled JS file.
402
427
  """
403
- return str(get_web_dir() / constants.Dirs.PAGES / (path + constants.Ext.JS))
428
+ return str(
429
+ get_web_dir()
430
+ / constants.Dirs.PAGES
431
+ / constants.Dirs.ROUTES
432
+ / (_path_to_file_stem(path) + constants.Ext.JS)
433
+ )
404
434
 
405
435
 
406
436
  def get_theme_path() -> str:
@@ -492,12 +522,8 @@ def add_meta(
492
522
  children.append(Description.create(content=description))
493
523
  children.append(Image.create(content=image))
494
524
 
495
- page.children.append(
496
- Head.create(
497
- *children,
498
- *meta_tags,
499
- )
500
- )
525
+ page.children.extend(children)
526
+ page.children.extend(meta_tags)
501
527
 
502
528
  return page
503
529
 
@@ -15,7 +15,6 @@ _SUBMODULES: set[str] = {
15
15
  "radix",
16
16
  "react_player",
17
17
  "sonner",
18
- "suneditor",
19
18
  "el",
20
19
  "base",
21
20
  "recharts",
@@ -26,7 +25,6 @@ _SUBMOD_ATTRS: dict[str, list[str]] = {
26
25
  "Component",
27
26
  "NoSSRComponent",
28
27
  ],
29
- "next": ["NextLink", "next_link"],
30
28
  }
31
29
  __getattr__, __dir__, __all__ = lazy_loader.attach(
32
30
  __name__,
@@ -16,8 +16,5 @@ from . import radix as radix
16
16
  from . import react_player as react_player
17
17
  from . import recharts as recharts
18
18
  from . import sonner as sonner
19
- from . import suneditor as suneditor
20
19
  from .component import Component as Component
21
20
  from .component import NoSSRComponent as NoSSRComponent
22
- from .next import NextLink as NextLink
23
- from .next import next_link as next_link