reflex 0.5.3a2__py3-none-any.whl → 0.5.4__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 (69) hide show
  1. reflex/.templates/apps/demo/code/webui/state.py +3 -2
  2. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +19 -20
  3. reflex/.templates/web/utils/state.js +6 -0
  4. reflex/__init__.py +7 -1
  5. reflex/__init__.pyi +2 -0
  6. reflex/app.py +2 -5
  7. reflex/compiler/compiler.py +2 -2
  8. reflex/components/chakra/base.py +3 -1
  9. reflex/components/chakra/forms/checkbox.py +1 -1
  10. reflex/components/chakra/forms/pininput.py +1 -1
  11. reflex/components/chakra/forms/rangeslider.py +1 -1
  12. reflex/components/chakra/navigation/link.py +3 -1
  13. reflex/components/component.py +43 -15
  14. reflex/components/core/banner.py +2 -1
  15. reflex/components/core/client_side_routing.py +3 -3
  16. reflex/components/core/client_side_routing.pyi +1 -0
  17. reflex/components/core/debounce.py +3 -1
  18. reflex/components/core/foreach.py +1 -1
  19. reflex/components/core/upload.py +2 -1
  20. reflex/components/datadisplay/code.py +4 -1
  21. reflex/components/datadisplay/dataeditor.py +11 -8
  22. reflex/components/datadisplay/dataeditor.pyi +1 -0
  23. reflex/components/el/elements/forms.py +25 -14
  24. reflex/components/el/elements/forms.pyi +2 -1
  25. reflex/components/markdown/markdown.py +17 -11
  26. reflex/components/markdown/markdown.pyi +12 -8
  27. reflex/components/plotly/plotly.py +82 -14
  28. reflex/components/plotly/plotly.pyi +15 -82
  29. reflex/components/radix/primitives/accordion.py +1 -1
  30. reflex/components/radix/themes/base.py +10 -2
  31. reflex/components/radix/themes/base.pyi +1 -0
  32. reflex/components/radix/themes/color_mode.py +1 -1
  33. reflex/components/radix/themes/components/checkbox.py +3 -1
  34. reflex/components/radix/themes/components/radio_group.py +6 -4
  35. reflex/components/radix/themes/components/separator.py +1 -1
  36. reflex/components/radix/themes/layout/container.py +1 -1
  37. reflex/components/radix/themes/layout/section.py +1 -1
  38. reflex/components/recharts/cartesian.py +42 -14
  39. reflex/components/recharts/cartesian.pyi +81 -17
  40. reflex/components/recharts/charts.py +12 -21
  41. reflex/components/recharts/charts.pyi +53 -14
  42. reflex/components/sonner/toast.py +37 -17
  43. reflex/components/sonner/toast.pyi +9 -5
  44. reflex/components/tags/tag.py +2 -1
  45. reflex/config.py +22 -14
  46. reflex/constants/__init__.py +2 -0
  47. reflex/constants/config.py +7 -0
  48. reflex/event.py +24 -15
  49. reflex/experimental/__init__.py +22 -2
  50. reflex/experimental/client_state.py +81 -23
  51. reflex/experimental/hooks.py +35 -35
  52. reflex/experimental/layout.py +17 -5
  53. reflex/experimental/layout.pyi +536 -0
  54. reflex/reflex.py +9 -5
  55. reflex/style.py +3 -2
  56. reflex/testing.py +44 -13
  57. reflex/utils/compat.py +5 -0
  58. reflex/utils/format.py +12 -2
  59. reflex/utils/processes.py +27 -0
  60. reflex/utils/pyi_generator.py +11 -4
  61. reflex/utils/serializers.py +114 -15
  62. reflex/utils/types.py +6 -2
  63. reflex/vars.py +57 -20
  64. reflex/vars.pyi +2 -2
  65. {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/METADATA +1 -1
  66. {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/RECORD +69 -68
  67. {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/LICENSE +0 -0
  68. {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/WHEEL +0 -0
  69. {reflex-0.5.3a2.dist-info → reflex-0.5.4.dist-info}/entry_points.txt +0 -0
@@ -66,7 +66,8 @@ class State(State):
66
66
  del self.chats[self.current_chat]
67
67
  if len(self.chats) == 0:
68
68
  self.chats = DEFAULT_CHATS
69
- self.current_chat = list(self.chats.keys())[0]
69
+ # set self.current_chat to the first chat.
70
+ self.current_chat = next(iter(self.chats))
70
71
  self.toggle_drawer()
71
72
 
72
73
  def set_chat(self, chat_name: str):
@@ -85,7 +86,7 @@ class State(State):
85
86
  Returns:
86
87
  The list of chat names.
87
88
  """
88
- return list(self.chats.keys())
89
+ return [*self.chats]
89
90
 
90
91
  async def process_question(self, form_data: dict[str, str]):
91
92
  """Get the response from the API.
@@ -1,22 +1,21 @@
1
- import { useTheme } from "next-themes"
2
- import { useEffect, useState } from "react"
3
- import { ColorModeContext, defaultColorMode } from "/utils/context.js"
4
-
1
+ import { useTheme } from "next-themes";
2
+ import { useEffect, useState } from "react";
3
+ import { ColorModeContext, defaultColorMode } from "/utils/context.js";
5
4
 
6
5
  export default function RadixThemesColorModeProvider({ children }) {
7
- const {theme, setTheme} = useTheme()
8
- const [colorMode, setColorMode] = useState(defaultColorMode)
9
-
10
- useEffect(() => {
11
- setColorMode(theme)
12
- }, [theme])
13
-
14
- const toggleColorMode = () => {
15
- setTheme(theme === "light" ? "dark" : "light")
16
- }
17
- return (
18
- <ColorModeContext.Provider value={[ colorMode, toggleColorMode ]}>
19
- {children}
20
- </ColorModeContext.Provider>
21
- )
22
- }
6
+ const { resolvedTheme, setTheme } = useTheme();
7
+ const [colorMode, setColorMode] = useState(defaultColorMode);
8
+
9
+ useEffect(() => {
10
+ setColorMode(resolvedTheme);
11
+ }, [resolvedTheme]);
12
+
13
+ const toggleColorMode = () => {
14
+ setTheme(resolvedTheme === "light" ? "dark" : "light");
15
+ };
16
+ return (
17
+ <ColorModeContext.Provider value={[colorMode, toggleColorMode]}>
18
+ {children}
19
+ </ColorModeContext.Provider>
20
+ );
21
+ }
@@ -358,6 +358,12 @@ export const connect = async (
358
358
  socket.current.on("connect_error", (error) => {
359
359
  setConnectErrors((connectErrors) => [connectErrors.slice(-9), error]);
360
360
  });
361
+
362
+ // When the socket disconnects reset the event_processing flag
363
+ socket.current.on("disconnect", () => {
364
+ event_processing = false;
365
+ });
366
+
361
367
  // On each received message, queue the updates and events.
362
368
  socket.current.on("event", (message) => {
363
369
  const update = JSON5.parse(message);
reflex/__init__.py CHANGED
@@ -248,7 +248,12 @@ _MAPPING: dict = {
248
248
  "admin": ["AdminDash"],
249
249
  "app": ["App", "UploadFile"],
250
250
  "base": ["Base"],
251
- "components.component": ["Component", "NoSSRComponent", "memo"],
251
+ "components.component": [
252
+ "Component",
253
+ "NoSSRComponent",
254
+ "memo",
255
+ "ComponentNamespace",
256
+ ],
252
257
  "components.el.elements.media": ["image"],
253
258
  "components.lucide": ["icon"],
254
259
  **COMPONENTS_BASE_MAPPING,
@@ -270,6 +275,7 @@ _MAPPING: dict = {
270
275
  "data_editor",
271
276
  "data_editor_theme",
272
277
  ],
278
+ "components.sonner.toast": ["toast"],
273
279
  "components.datadisplay.logo": ["logo"],
274
280
  "components.gridjs": ["data_table"],
275
281
  "components.moment": ["MomentDelta", "moment"],
reflex/__init__.pyi CHANGED
@@ -24,6 +24,7 @@ from .base import Base as Base
24
24
  from .components.component import Component as Component
25
25
  from .components.component import NoSSRComponent as NoSSRComponent
26
26
  from .components.component import memo as memo
27
+ from .components.component import ComponentNamespace as ComponentNamespace
27
28
  from .components.el.elements.media import image as image
28
29
  from .components.lucide import icon as icon
29
30
  from .components.base.fragment import fragment as fragment
@@ -143,6 +144,7 @@ from .components.core.upload import upload as upload
143
144
  from .components.datadisplay.code import code_block as code_block
144
145
  from .components.datadisplay.dataeditor import data_editor as data_editor
145
146
  from .components.datadisplay.dataeditor import data_editor_theme as data_editor_theme
147
+ from .components.sonner.toast import toast as toast
146
148
  from .components.datadisplay.logo import logo as logo
147
149
  from .components.gridjs import data_table as data_table
148
150
  from .components.moment import MomentDelta as MomentDelta
reflex/app.py CHANGED
@@ -707,11 +707,8 @@ class App(LifespanMixin, Base):
707
707
  page_imports = {
708
708
  i
709
709
  for i, tags in imports.items()
710
- if i
711
- not in [
712
- *constants.PackageJson.DEPENDENCIES.keys(),
713
- *constants.PackageJson.DEV_DEPENDENCIES.keys(),
714
- ]
710
+ if i not in constants.PackageJson.DEPENDENCIES
711
+ and i not in constants.PackageJson.DEV_DEPENDENCIES
715
712
  and not any(i.startswith(prefix) for prefix in ["/", ".", "next/"])
716
713
  and i != ""
717
714
  and any(tag.install for tag in tags)
@@ -17,7 +17,7 @@ from reflex.components.component import (
17
17
  )
18
18
  from reflex.config import get_config
19
19
  from reflex.state import BaseState
20
- from reflex.style import LIGHT_COLOR_MODE
20
+ from reflex.style import SYSTEM_COLOR_MODE
21
21
  from reflex.utils.exec import is_prod_mode
22
22
  from reflex.utils.imports import ImportVar
23
23
  from reflex.vars import Var
@@ -79,7 +79,7 @@ def _compile_contexts(state: Optional[Type[BaseState]], theme: Component | None)
79
79
  """
80
80
  appearance = getattr(theme, "appearance", None)
81
81
  if appearance is None:
82
- appearance = LIGHT_COLOR_MODE
82
+ appearance = SYSTEM_COLOR_MODE
83
83
  return (
84
84
  templates.CONTEXT.render(
85
85
  initial_state=utils.compile_state(state),
@@ -65,7 +65,9 @@ class ChakraProvider(ChakraComponent):
65
65
  A new ChakraProvider component.
66
66
  """
67
67
  return super().create(
68
- theme=Var.create("extendTheme(theme)", _var_is_local=False),
68
+ theme=Var.create(
69
+ "extendTheme(theme)", _var_is_local=False, _var_is_string=False
70
+ ),
69
71
  )
70
72
 
71
73
  def _get_imports(self) -> imports.ImportDict:
@@ -51,7 +51,7 @@ class Checkbox(ChakraComponent):
51
51
  name: Var[str]
52
52
 
53
53
  # The value of the input field when checked (use is_checked prop for a bool)
54
- value: Var[str] = Var.create("true") # type: ignore
54
+ value: Var[str] = Var.create("true", _var_is_string=True) # type: ignore
55
55
 
56
56
  # The spacing between the checkbox and its label text (0.5rem)
57
57
  spacing: Var[str]
@@ -122,7 +122,7 @@ class PinInput(ChakraComponent):
122
122
  if ref:
123
123
  return (
124
124
  f"const {ref} = {str(refs_declaration)}; "
125
- f"{str(Var.create_safe(ref).as_ref())} = {ref}"
125
+ f"{str(Var.create_safe(ref, _var_is_string=False).as_ref())} = {ref}"
126
126
  )
127
127
  return super()._get_ref_hook()
128
128
 
@@ -80,7 +80,7 @@ class RangeSlider(ChakraComponent):
80
80
  if ref:
81
81
  return (
82
82
  f"const {ref} = Array.from({{length:2}}, () => useRef(null)); "
83
- f"{str(Var.create_safe(ref).as_ref())} = {ref}"
83
+ f"{str(Var.create_safe(ref, _var_is_string=False).as_ref())} = {ref}"
84
84
  )
85
85
  return super()._get_ref_hook()
86
86
 
@@ -25,7 +25,9 @@ class Link(ChakraComponent):
25
25
  text: Var[str]
26
26
 
27
27
  # What the link renders to.
28
- as_: Var[str] = BaseVar.create(value="{NextLink}", _var_is_local=False) # type: ignore
28
+ as_: Var[str] = BaseVar.create(
29
+ value="{NextLink}", _var_is_local=False, _var_is_string=False
30
+ ) # type: ignore
29
31
 
30
32
  # If true, the link will open in new tab.
31
33
  is_external: Var[bool]
@@ -319,7 +319,9 @@ class Component(BaseComponent, ABC):
319
319
  # Set default values for any props.
320
320
  if types._issubclass(field.type_, Var):
321
321
  field.required = False
322
- field.default = Var.create(field.default)
322
+ field.default = Var.create(
323
+ field.default, _var_is_string=isinstance(field.default, str)
324
+ )
323
325
  elif types._issubclass(field.type_, EventHandler):
324
326
  field.required = False
325
327
 
@@ -348,7 +350,10 @@ class Component(BaseComponent, ABC):
348
350
  "id": kwargs.get("id"),
349
351
  "children": children,
350
352
  **{
351
- prop: Var.create(kwargs[prop])
353
+ prop: Var.create(
354
+ kwargs[prop],
355
+ _var_is_string=False if isinstance(kwargs[prop], str) else None,
356
+ )
352
357
  for prop in self.get_initial_props()
353
358
  if prop in kwargs
354
359
  },
@@ -360,7 +365,6 @@ class Component(BaseComponent, ABC):
360
365
  # Get the component fields, triggers, and props.
361
366
  fields = self.get_fields()
362
367
  component_specific_triggers = self.get_event_triggers()
363
- triggers = component_specific_triggers.keys()
364
368
  props = self.get_props()
365
369
 
366
370
  # Add any events triggers.
@@ -370,13 +374,17 @@ class Component(BaseComponent, ABC):
370
374
 
371
375
  # Iterate through the kwargs and set the props.
372
376
  for key, value in kwargs.items():
373
- if key.startswith("on_") and key not in triggers and key not in props:
377
+ if (
378
+ key.startswith("on_")
379
+ and key not in component_specific_triggers
380
+ and key not in props
381
+ ):
374
382
  raise ValueError(
375
383
  f"The {(comp_name := type(self).__name__)} does not take in an `{key}` event trigger. If {comp_name}"
376
384
  f" is a third party component make sure to add `{key}` to the component's event triggers. "
377
385
  f"visit https://reflex.dev/docs/wrapping-react/guide/#event-triggers for more info."
378
386
  )
379
- if key in triggers:
387
+ if key in component_specific_triggers:
380
388
  # Event triggers are bound to event chains.
381
389
  field_type = EventChain
382
390
  elif key in props:
@@ -392,7 +400,10 @@ class Component(BaseComponent, ABC):
392
400
  passed_types = None
393
401
  try:
394
402
  # Try to create a var from the value.
395
- kwargs[key] = Var.create(value)
403
+ kwargs[key] = Var.create(
404
+ value,
405
+ _var_is_string=False if isinstance(value, str) else None,
406
+ )
396
407
 
397
408
  # Check that the var type is not None.
398
409
  if kwargs[key] is None:
@@ -417,7 +428,9 @@ class Component(BaseComponent, ABC):
417
428
  if types.is_union(passed_type):
418
429
  # We need to check all possible types in the union.
419
430
  passed_types = (
420
- arg for arg in passed_type.__args__ if arg is not type(None)
431
+ arg
432
+ for arg in passed_type.__args__ # type: ignore
433
+ if arg is not type(None)
421
434
  )
422
435
  if (
423
436
  # If the passed var is a union, check if all possible types are valid.
@@ -436,10 +449,11 @@ class Component(BaseComponent, ABC):
436
449
  )
437
450
 
438
451
  # Check if the key is an event trigger.
439
- if key in triggers:
452
+ if key in component_specific_triggers:
440
453
  # Temporarily disable full control for event triggers.
441
454
  kwargs["event_triggers"][key] = self._create_event_chain(
442
- value=value, args_spec=component_specific_triggers[key]
455
+ value=value, # type: ignore
456
+ args_spec=component_specific_triggers[key],
443
457
  )
444
458
 
445
459
  # Remove any keys that were added as events.
@@ -519,13 +533,23 @@ class Component(BaseComponent, ABC):
519
533
  events.append(event)
520
534
  elif isinstance(v, Callable):
521
535
  # Call the lambda to get the event chain.
522
- events.extend(call_event_fn(v, args_spec))
536
+ result = call_event_fn(v, args_spec)
537
+ if isinstance(result, Var):
538
+ raise ValueError(
539
+ f"Invalid event chain: {v}. Cannot use a Var-returning "
540
+ "lambda inside an EventChain list."
541
+ )
542
+ events.extend(result)
523
543
  else:
524
544
  raise ValueError(f"Invalid event: {v}")
525
545
 
526
546
  # If the input is a callable, create an event chain.
527
547
  elif isinstance(value, Callable):
528
- events = call_event_fn(value, args_spec)
548
+ result = call_event_fn(value, args_spec)
549
+ if isinstance(result, Var):
550
+ # Recursively call this function if the lambda returned an EventChain Var.
551
+ return self._create_event_chain(args_spec, result)
552
+ events = result
529
553
 
530
554
  # Otherwise, raise an error.
531
555
  else:
@@ -659,7 +683,9 @@ class Component(BaseComponent, ABC):
659
683
  # Add ref to element if `id` is not None.
660
684
  ref = self.get_ref()
661
685
  if ref is not None:
662
- props["ref"] = Var.create(ref, _var_is_local=False)
686
+ props["ref"] = Var.create(
687
+ ref, _var_is_local=False, _var_is_string=False
688
+ )
663
689
  else:
664
690
  props = props.copy()
665
691
 
@@ -1078,7 +1104,9 @@ class Component(BaseComponent, ABC):
1078
1104
  vars.append(comp_prop)
1079
1105
  elif isinstance(comp_prop, str):
1080
1106
  # Collapse VarData encoded in f-strings.
1081
- var = Var.create_safe(comp_prop)
1107
+ var = Var.create_safe(
1108
+ comp_prop, _var_is_string=isinstance(comp_prop, str)
1109
+ )
1082
1110
  if var._var_data is not None:
1083
1111
  vars.append(var)
1084
1112
 
@@ -1375,7 +1403,7 @@ class Component(BaseComponent, ABC):
1375
1403
  """
1376
1404
  ref = self.get_ref()
1377
1405
  if ref is not None:
1378
- return f"const {ref} = useRef(null); {str(Var.create_safe(ref).as_ref())} = {ref};"
1406
+ return f"const {ref} = useRef(null); {str(Var.create_safe(ref, _var_is_string=False).as_ref())} = {ref};"
1379
1407
 
1380
1408
  def _get_vars_hooks(self) -> dict[str, None]:
1381
1409
  """Get the hooks required by vars referenced in this component.
@@ -2134,7 +2162,7 @@ class StatefulComponent(BaseComponent):
2134
2162
 
2135
2163
  # Store the memoized function name and hook code for this event trigger.
2136
2164
  trigger_memo[event_trigger] = (
2137
- Var.create_safe(memo_name)._replace(
2165
+ Var.create_safe(memo_name, _var_is_string=False)._replace(
2138
2166
  _var_type=EventChain, merge_var_data=memo_var_data
2139
2167
  ),
2140
2168
  f"const {memo_name} = useCallback({rendered_chain}, [{', '.join(var_deps)}])",
@@ -132,7 +132,8 @@ useEffect(() => {{
132
132
  toast.dismiss("{toast_id}");
133
133
  setUserDismissed(false); // after reconnection reset dismissed state
134
134
  }}
135
- }}, [{connect_errors}]);"""
135
+ }}, [{connect_errors}]);""",
136
+ _var_is_string=False,
136
137
  )
137
138
 
138
139
  hook._var_data = VarData.merge( # type: ignore
@@ -14,7 +14,7 @@ from reflex.components.component import Component
14
14
  from reflex.components.core.cond import cond
15
15
  from reflex.vars import Var
16
16
 
17
- route_not_found: Var = Var.create_safe(constants.ROUTE_NOT_FOUND)
17
+ route_not_found: Var = Var.create_safe(constants.ROUTE_NOT_FOUND, _var_is_string=False)
18
18
 
19
19
 
20
20
  class ClientSideRouting(Component):
@@ -23,13 +23,13 @@ class ClientSideRouting(Component):
23
23
  library = "/utils/client_side_routing"
24
24
  tag = "useClientSideRouting"
25
25
 
26
- def _get_hooks(self) -> str:
26
+ def add_hooks(self) -> list[str]:
27
27
  """Get the hooks to render.
28
28
 
29
29
  Returns:
30
30
  The useClientSideRouting hook.
31
31
  """
32
- return f"const {constants.ROUTE_NOT_FOUND} = {self.tag}()"
32
+ return [f"const {constants.ROUTE_NOT_FOUND} = {self.tag}()"]
33
33
 
34
34
  def render(self) -> str:
35
35
  """Render the component.
@@ -15,6 +15,7 @@ from reflex.vars import Var
15
15
  route_not_found: Var
16
16
 
17
17
  class ClientSideRouting(Component):
18
+ def add_hooks(self) -> list[str]: ...
18
19
  def render(self) -> str: ...
19
20
  @overload
20
21
  @classmethod
@@ -99,7 +99,9 @@ class DebounceInput(Component):
99
99
  props["class_name"] = f"{props.get('class_name', '')} {child.class_name}"
100
100
  child_ref = child.get_ref()
101
101
  if props.get("input_ref") is None and child_ref:
102
- props["input_ref"] = Var.create_safe(child_ref, _var_is_local=False)
102
+ props["input_ref"] = Var.create_safe(
103
+ child_ref, _var_is_local=False, _var_is_string=False
104
+ )
103
105
  props["id"] = child.id
104
106
 
105
107
  # Set the child element to wrap, including any imports/hooks from the child.
@@ -60,7 +60,7 @@ class Foreach(Component):
60
60
  deprecation_version="0.5.0",
61
61
  removal_version="0.6.0",
62
62
  )
63
- iterable = Var.create_safe(iterable)
63
+ iterable = Var.create_safe(iterable, _var_is_string=False)
64
64
  if iterable._var_type == Any:
65
65
  raise ForeachVarError(
66
66
  f"Could not foreach over var `{iterable._var_full_name}` of type Any. "
@@ -119,6 +119,7 @@ def get_upload_dir() -> Path:
119
119
 
120
120
  uploaded_files_url_prefix: Var = Var.create_safe(
121
121
  "${getBackendURL(env.UPLOAD)}",
122
+ _var_is_string=False,
122
123
  _var_data=VarData(
123
124
  imports={
124
125
  f"/{Dirs.STATE_PATH}": [imports.ImportVar(tag="getBackendURL")],
@@ -230,7 +231,7 @@ class Upload(MemoizationLeaf):
230
231
  key: value for key, value in props.items() if key in supported_props
231
232
  }
232
233
  # The file input to use.
233
- upload = Input.create(type_="file")
234
+ upload = Input.create(type="file")
234
235
  upload.special_props = {
235
236
  BaseVar(_var_name="{...getInputProps()}", _var_type=None)
236
237
  }
@@ -504,10 +504,13 @@ class CodeBlock(Component):
504
504
  style=Var.create(
505
505
  format.to_camel_case(f"{predicate}{qmark}{value.replace('`', '')}"),
506
506
  _var_is_local=False,
507
+ _var_is_string=False,
507
508
  )
508
509
  ).remove_props("theme", "code")
509
510
  if self.code is not None:
510
- out.special_props.add(Var.create_safe(f"children={str(self.code)}"))
511
+ out.special_props.add(
512
+ Var.create_safe(f"children={str(self.code)}", _var_is_string=False)
513
+ )
511
514
  return out
512
515
 
513
516
  @staticmethod
@@ -252,13 +252,20 @@ class DataEditor(NoSSRComponent):
252
252
  "on_column_resize": lambda col, width: [col, width],
253
253
  }
254
254
 
255
- def _get_hooks(self) -> str | None:
255
+ def add_hooks(self) -> list[str]:
256
+ """Get the hooks to render.
257
+
258
+ Returns:
259
+ The hooks to render.
260
+ """
256
261
  # Define the id of the component in case multiple are used in the same page.
257
262
  editor_id = get_unique_variable_name()
258
263
 
259
264
  # Define the name of the getData callback associated with this component and assign to get_cell_content.
260
265
  data_callback = f"getData_{editor_id}"
261
- self.get_cell_content = Var.create(data_callback, _var_is_local=False) # type: ignore
266
+ self.get_cell_content = Var.create(
267
+ data_callback, _var_is_local=False, _var_is_string=False
268
+ ) # type: ignore
262
269
 
263
270
  code = [f"function {data_callback}([col, row])" "{"]
264
271
 
@@ -272,7 +279,7 @@ class DataEditor(NoSSRComponent):
272
279
  ]
273
280
  )
274
281
 
275
- return "\n".join(code)
282
+ return ["\n".join(code)]
276
283
 
277
284
  @classmethod
278
285
  def create(cls, *children, **props) -> Component:
@@ -296,11 +303,7 @@ class DataEditor(NoSSRComponent):
296
303
 
297
304
  # If rows is not provided, determine from data.
298
305
  if rows is None:
299
- props["rows"] = (
300
- data.length() # BaseVar.create(value=f"{data}.length()", is_local=False)
301
- if isinstance(data, Var)
302
- else len(data)
303
- )
306
+ props["rows"] = data.length() if isinstance(data, Var) else len(data)
304
307
 
305
308
  if not isinstance(columns, Var) and len(columns):
306
309
  if (
@@ -81,6 +81,7 @@ class DataEditorTheme(Base):
81
81
 
82
82
  class DataEditor(NoSSRComponent):
83
83
  def get_event_triggers(self) -> Dict[str, Callable]: ...
84
+ def add_hooks(self) -> list[str]: ...
84
85
  @overload
85
86
  @classmethod
86
87
  def create( # type: ignore
@@ -17,7 +17,7 @@ from reflex.vars import BaseVar, Var
17
17
 
18
18
  from .base import BaseHTML
19
19
 
20
- FORM_DATA = Var.create("form_data")
20
+ FORM_DATA = Var.create("form_data", _var_is_string=False)
21
21
  HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
22
22
  """
23
23
  const handleSubmit_{{ handle_submit_unique_name }} = useCallback((ev) => {
@@ -181,18 +181,25 @@ class Form(BaseHTML):
181
181
  },
182
182
  )
183
183
 
184
- def _get_hooks(self) -> str | None:
184
+ def add_hooks(self) -> list[str]:
185
+ """Add hooks for the form.
186
+
187
+ Returns:
188
+ The hooks for the form.
189
+ """
185
190
  if EventTriggers.ON_SUBMIT not in self.event_triggers:
186
- return
187
- return HANDLE_SUBMIT_JS_JINJA2.render(
188
- handle_submit_unique_name=self.handle_submit_unique_name,
189
- form_data=FORM_DATA,
190
- field_ref_mapping=str(Var.create_safe(self._get_form_refs())),
191
- on_submit_event_chain=format_event_chain(
192
- self.event_triggers[EventTriggers.ON_SUBMIT]
193
- ),
194
- reset_on_submit=self.reset_on_submit,
195
- )
191
+ return []
192
+ return [
193
+ HANDLE_SUBMIT_JS_JINJA2.render(
194
+ handle_submit_unique_name=self.handle_submit_unique_name,
195
+ form_data=FORM_DATA,
196
+ field_ref_mapping=str(Var.create_safe(self._get_form_refs())),
197
+ on_submit_event_chain=format_event_chain(
198
+ self.event_triggers[EventTriggers.ON_SUBMIT]
199
+ ),
200
+ reset_on_submit=self.reset_on_submit,
201
+ )
202
+ ]
196
203
 
197
204
  def _render(self) -> Tag:
198
205
  render_tag = super()._render()
@@ -214,17 +221,19 @@ class Form(BaseHTML):
214
221
  # when ref start with refs_ it's an array of refs, so we need different method
215
222
  # to collect data
216
223
  if ref.startswith("refs_"):
217
- ref_var = Var.create_safe(ref[:-3]).as_ref()
224
+ ref_var = Var.create_safe(ref[:-3], _var_is_string=False).as_ref()
218
225
  form_refs[ref[5:-3]] = Var.create_safe(
219
226
  f"getRefValues({str(ref_var)})",
220
227
  _var_is_local=False,
228
+ _var_is_string=False,
221
229
  _var_data=ref_var._var_data,
222
230
  )
223
231
  else:
224
- ref_var = Var.create_safe(ref).as_ref()
232
+ ref_var = Var.create_safe(ref, _var_is_string=False).as_ref()
225
233
  form_refs[ref[4:]] = Var.create_safe(
226
234
  f"getRefValue({str(ref_var)})",
227
235
  _var_is_local=False,
236
+ _var_is_string=False,
228
237
  _var_data=ref_var._var_data,
229
238
  )
230
239
  return form_refs
@@ -623,6 +632,7 @@ class Textarea(BaseHTML):
623
632
  on_key_down=Var.create_safe(
624
633
  f"(e) => enterKeySubmitOnKeyDown(e, {self.enter_key_submit._var_name_unwrapped})",
625
634
  _var_is_local=False,
635
+ _var_is_string=False,
626
636
  _var_data=self.enter_key_submit._var_data,
627
637
  )
628
638
  )
@@ -631,6 +641,7 @@ class Textarea(BaseHTML):
631
641
  on_input=Var.create_safe(
632
642
  f"(e) => autoHeightOnInput(e, {self.auto_height._var_name_unwrapped})",
633
643
  _var_is_local=False,
644
+ _var_is_string=False,
634
645
  _var_data=self.auto_height._var_data,
635
646
  )
636
647
  )
@@ -19,7 +19,7 @@ from reflex.utils.format import format_event_chain
19
19
  from reflex.vars import BaseVar, Var
20
20
  from .base import BaseHTML
21
21
 
22
- FORM_DATA = Var.create("form_data")
22
+ FORM_DATA = Var.create("form_data", _var_is_string=False)
23
23
  HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
24
24
  "\n const handleSubmit_{{ handle_submit_unique_name }} = useCallback((ev) => {\n const $form = ev.target\n ev.preventDefault()\n const {{ form_data }} = {...Object.fromEntries(new FormData($form).entries()), ...{{ field_ref_mapping }}}\n\n {{ on_submit_event_chain }}\n\n if ({{ reset_on_submit }}) {\n $form.reset()\n }\n })\n "
25
25
  )
@@ -581,6 +581,7 @@ class Form(BaseHTML):
581
581
  The form component.
582
582
  """
583
583
  ...
584
+ def add_hooks(self) -> list[str]: ...
584
585
 
585
586
  class Input(BaseHTML):
586
587
  def get_event_triggers(self) -> Dict[str, Any]: ...