reflex 0.5.0.post1__py3-none-any.whl → 0.5.1a1__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 (41) hide show
  1. reflex/.templates/web/utils/state.js +7 -2
  2. reflex/app.py +69 -50
  3. reflex/base.py +5 -2
  4. reflex/components/component.py +49 -13
  5. reflex/components/core/__init__.py +7 -1
  6. reflex/components/core/banner.py +79 -6
  7. reflex/components/core/banner.pyi +130 -0
  8. reflex/components/core/cond.py +1 -1
  9. reflex/components/core/debounce.py +2 -4
  10. reflex/components/core/foreach.py +11 -0
  11. reflex/components/core/upload.py +9 -10
  12. reflex/components/el/elements/forms.py +12 -6
  13. reflex/components/el/elements/media.py +19 -0
  14. reflex/components/el/elements/media.pyi +3 -1
  15. reflex/components/gridjs/datatable.py +4 -2
  16. reflex/components/props.py +30 -0
  17. reflex/components/radix/themes/components/tabs.py +1 -1
  18. reflex/components/sonner/toast.py +102 -35
  19. reflex/components/sonner/toast.pyi +27 -14
  20. reflex/config.py +5 -3
  21. reflex/constants/compiler.py +3 -3
  22. reflex/constants/installer.py +1 -1
  23. reflex/event.py +38 -24
  24. reflex/experimental/__init__.py +4 -0
  25. reflex/experimental/client_state.py +198 -0
  26. reflex/state.py +61 -21
  27. reflex/style.py +3 -3
  28. reflex/testing.py +28 -9
  29. reflex/utils/exceptions.py +64 -8
  30. reflex/utils/format.py +73 -2
  31. reflex/utils/prerequisites.py +28 -18
  32. reflex/utils/processes.py +34 -4
  33. reflex/utils/telemetry.py +5 -5
  34. reflex/utils/types.py +16 -0
  35. reflex/vars.py +104 -61
  36. reflex/vars.pyi +7 -6
  37. {reflex-0.5.0.post1.dist-info → reflex-0.5.1a1.dist-info}/METADATA +1 -1
  38. {reflex-0.5.0.post1.dist-info → reflex-0.5.1a1.dist-info}/RECORD +41 -39
  39. {reflex-0.5.0.post1.dist-info → reflex-0.5.1a1.dist-info}/LICENSE +0 -0
  40. {reflex-0.5.0.post1.dist-info → reflex-0.5.1a1.dist-info}/WHEEL +0 -0
  41. {reflex-0.5.0.post1.dist-info → reflex-0.5.1a1.dist-info}/entry_points.txt +0 -0
reflex/config.py CHANGED
@@ -251,8 +251,10 @@ class Config(Base):
251
251
  The updated config values.
252
252
 
253
253
  Raises:
254
- ValueError: If an environment variable is set to an invalid type.
254
+ EnvVarValueError: If an environment variable is set to an invalid type.
255
255
  """
256
+ from reflex.utils.exceptions import EnvVarValueError
257
+
256
258
  updated_values = {}
257
259
  # Iterate over the fields.
258
260
  for key, field in self.__fields__.items():
@@ -273,11 +275,11 @@ class Config(Base):
273
275
  env_var = env_var.lower() in ["true", "1", "yes"]
274
276
  else:
275
277
  env_var = field.type_(env_var)
276
- except ValueError:
278
+ except ValueError as ve:
277
279
  console.error(
278
280
  f"Could not convert {key.upper()}={env_var} to type {field.type_}"
279
281
  )
280
- raise
282
+ raise EnvVarValueError from ve
281
283
 
282
284
  # Set the value.
283
285
  updated_values[key] = env_var
@@ -103,9 +103,9 @@ class Imports(SimpleNamespace):
103
103
  """Common sets of import vars."""
104
104
 
105
105
  EVENTS = {
106
- "react": {ImportVar(tag="useContext")},
107
- f"/{Dirs.CONTEXTS_PATH}": {ImportVar(tag="EventLoopContext")},
108
- f"/{Dirs.STATE_PATH}": {ImportVar(tag=CompileVars.TO_EVENT)},
106
+ "react": [ImportVar(tag="useContext")],
107
+ f"/{Dirs.CONTEXTS_PATH}": [ImportVar(tag="EventLoopContext")],
108
+ f"/{Dirs.STATE_PATH}": [ImportVar(tag=CompileVars.TO_EVENT)],
109
109
  }
110
110
 
111
111
 
@@ -35,7 +35,7 @@ class Bun(SimpleNamespace):
35
35
  """Bun constants."""
36
36
 
37
37
  # The Bun version.
38
- VERSION = "1.1.6"
38
+ VERSION = "1.1.8"
39
39
  # Min Bun Version
40
40
  MIN_VERSION = "0.7.0"
41
41
  # The directory to store the bun.
reflex/event.py CHANGED
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import inspect
6
6
  from base64 import b64encode
7
- from types import FunctionType
8
7
  from typing import (
9
8
  Any,
10
9
  Callable,
@@ -180,8 +179,10 @@ class EventHandler(EventActionsMixin):
180
179
  The event spec, containing both the function and args.
181
180
 
182
181
  Raises:
183
- TypeError: If the arguments are invalid.
182
+ EventHandlerTypeError: If the arguments are invalid.
184
183
  """
184
+ from reflex.utils.exceptions import EventHandlerTypeError
185
+
185
186
  # Get the function args.
186
187
  fn_args = inspect.getfullargspec(self.fn).args[1:]
187
188
  fn_args = (Var.create_safe(arg) for arg in fn_args)
@@ -197,7 +198,7 @@ class EventHandler(EventActionsMixin):
197
198
  try:
198
199
  values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
199
200
  except TypeError as e:
200
- raise TypeError(
201
+ raise EventHandlerTypeError(
201
202
  f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
202
203
  ) from e
203
204
  payload = tuple(zip(fn_args, values))
@@ -256,8 +257,10 @@ class EventSpec(EventActionsMixin):
256
257
  The event spec with the new arguments.
257
258
 
258
259
  Raises:
259
- TypeError: If the arguments are invalid.
260
+ EventHandlerTypeError: If the arguments are invalid.
260
261
  """
262
+ from reflex.utils.exceptions import EventHandlerTypeError
263
+
261
264
  # Get the remaining unfilled function args.
262
265
  fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :]
263
266
  fn_args = (Var.create_safe(arg) for arg in fn_args)
@@ -268,7 +271,7 @@ class EventSpec(EventActionsMixin):
268
271
  try:
269
272
  values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
270
273
  except TypeError as e:
271
- raise TypeError(
274
+ raise EventHandlerTypeError(
272
275
  f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
273
276
  ) from e
274
277
  new_payload = tuple(zip(fn_args, values))
@@ -312,10 +315,12 @@ class CallableEventSpec(EventSpec):
312
315
  The EventSpec returned from calling the function.
313
316
 
314
317
  Raises:
315
- TypeError: If the CallableEventSpec has no associated function.
318
+ EventHandlerTypeError: If the CallableEventSpec has no associated function.
316
319
  """
320
+ from reflex.utils.exceptions import EventHandlerTypeError
321
+
317
322
  if self.fn is None:
318
- raise TypeError("CallableEventSpec has no associated function.")
323
+ raise EventHandlerTypeError("CallableEventSpec has no associated function.")
319
324
  return self.fn(*args, **kwargs)
320
325
 
321
326
 
@@ -462,18 +467,27 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
462
467
  )
463
468
 
464
469
 
465
- def redirect(path: str | Var[str], external: Optional[bool] = False) -> EventSpec:
470
+ def redirect(
471
+ path: str | Var[str],
472
+ external: Optional[bool] = False,
473
+ replace: Optional[bool] = False,
474
+ ) -> EventSpec:
466
475
  """Redirect to a new path.
467
476
 
468
477
  Args:
469
478
  path: The path to redirect to.
470
479
  external: Whether to open in new tab or not.
480
+ replace: If True, the current page will not create a new history entry.
471
481
 
472
482
  Returns:
473
483
  An event to redirect to the path.
474
484
  """
475
485
  return server_side(
476
- "_redirect", get_fn_signature(redirect), path=path, external=external
486
+ "_redirect",
487
+ get_fn_signature(redirect),
488
+ path=path,
489
+ external=external,
490
+ replace=replace,
477
491
  )
478
492
 
479
493
 
@@ -700,7 +714,11 @@ def _callback_arg_spec(eval_result):
700
714
 
701
715
  def call_script(
702
716
  javascript_code: str,
703
- callback: EventHandler | Callable | None = None,
717
+ callback: EventSpec
718
+ | EventHandler
719
+ | Callable
720
+ | List[EventSpec | EventHandler | Callable]
721
+ | None = None,
704
722
  ) -> EventSpec:
705
723
  """Create an event handler that executes arbitrary javascript code.
706
724
 
@@ -710,21 +728,14 @@ def call_script(
710
728
 
711
729
  Returns:
712
730
  EventSpec: An event that will execute the client side javascript.
713
-
714
- Raises:
715
- ValueError: If the callback is not a valid event handler.
716
731
  """
717
732
  callback_kwargs = {}
718
733
  if callback is not None:
719
- arg_name = parse_args_spec(_callback_arg_spec)[0]._var_name
720
- if isinstance(callback, EventHandler):
721
- event_spec = call_event_handler(callback, _callback_arg_spec)
722
- elif isinstance(callback, FunctionType):
723
- event_spec = call_event_fn(callback, _callback_arg_spec)[0]
724
- else:
725
- raise ValueError("Cannot use {callback!r} as a call_script callback.")
726
734
  callback_kwargs = {
727
- "callback": f"({arg_name}) => queueEvents([{format.format_event(event_spec)}], {constants.CompileVars.SOCKET})"
735
+ "callback": format.format_queue_events(
736
+ callback,
737
+ args_spec=lambda result: [result],
738
+ )
728
739
  }
729
740
  return server_side(
730
741
  "_call_script",
@@ -834,10 +845,11 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
834
845
  The event specs from calling the function.
835
846
 
836
847
  Raises:
837
- ValueError: If the lambda has an invalid signature.
848
+ EventHandlerValueError: If the lambda has an invalid signature.
838
849
  """
839
850
  # Import here to avoid circular imports.
840
851
  from reflex.event import EventHandler, EventSpec
852
+ from reflex.utils.exceptions import EventHandlerValueError
841
853
 
842
854
  # Get the args of the lambda.
843
855
  args = inspect.getfullargspec(fn).args
@@ -851,7 +863,7 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
851
863
  elif len(args) == 1:
852
864
  out = fn(arg)
853
865
  else:
854
- raise ValueError(f"Lambda {fn} must have 0 or 1 arguments.")
866
+ raise EventHandlerValueError(f"Lambda {fn} must have 0 or 1 arguments.")
855
867
 
856
868
  # Convert the output to a list.
857
869
  if not isinstance(out, List):
@@ -869,7 +881,9 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
869
881
 
870
882
  # Make sure the event spec is valid.
871
883
  if not isinstance(e, EventSpec):
872
- raise ValueError(f"Lambda {fn} returned an invalid event spec: {e}.")
884
+ raise EventHandlerValueError(
885
+ f"Lambda {fn} returned an invalid event spec: {e}."
886
+ )
873
887
 
874
888
  # Add the event spec to the chain.
875
889
  events.append(e)
@@ -2,11 +2,13 @@
2
2
 
3
3
  from types import SimpleNamespace
4
4
 
5
+ from reflex.components.props import PropsBase
5
6
  from reflex.components.radix.themes.components.progress import progress as progress
6
7
  from reflex.components.sonner.toast import toast as toast
7
8
 
8
9
  from ..utils.console import warn
9
10
  from . import hooks as hooks
11
+ from .client_state import ClientStateVar as ClientStateVar
10
12
  from .layout import layout as layout
11
13
  from .misc import run_in_thread as run_in_thread
12
14
 
@@ -15,9 +17,11 @@ warn(
15
17
  )
16
18
 
17
19
  _x = SimpleNamespace(
20
+ client_state=ClientStateVar.create,
18
21
  hooks=hooks,
19
22
  layout=layout,
20
23
  progress=progress,
24
+ PropsBase=PropsBase,
21
25
  run_in_thread=run_in_thread,
22
26
  toast=toast,
23
27
  )
@@ -0,0 +1,198 @@
1
+ """Handle client side state with `useState`."""
2
+
3
+ import dataclasses
4
+ import sys
5
+ from typing import Any, Callable, Optional, Type
6
+
7
+ from reflex import constants
8
+ from reflex.event import EventChain, EventHandler, EventSpec, call_script
9
+ from reflex.utils.imports import ImportVar
10
+ from reflex.vars import Var, VarData
11
+
12
+
13
+ def _client_state_ref(var_name: str) -> str:
14
+ """Get the ref path for a ClientStateVar.
15
+
16
+ Args:
17
+ var_name: The name of the variable.
18
+
19
+ Returns:
20
+ An accessor for ClientStateVar ref as a string.
21
+ """
22
+ return f"refs['_client_state_{var_name}']"
23
+
24
+
25
+ @dataclasses.dataclass(
26
+ eq=False,
27
+ **{"slots": True} if sys.version_info >= (3, 10) else {},
28
+ )
29
+ class ClientStateVar(Var):
30
+ """A Var that exists on the client via useState."""
31
+
32
+ # The name of the var.
33
+ _var_name: str = dataclasses.field()
34
+
35
+ # Track the names of the getters and setters
36
+ _setter_name: str = dataclasses.field()
37
+ _getter_name: str = dataclasses.field()
38
+
39
+ # The type of the var.
40
+ _var_type: Type = dataclasses.field(default=Any)
41
+
42
+ # Whether this is a local javascript variable.
43
+ _var_is_local: bool = dataclasses.field(default=False)
44
+
45
+ # Whether the var is a string literal.
46
+ _var_is_string: bool = dataclasses.field(default=False)
47
+
48
+ # _var_full_name should be prefixed with _var_state
49
+ _var_full_name_needs_state_prefix: bool = dataclasses.field(default=False)
50
+
51
+ # Extra metadata associated with the Var
52
+ _var_data: Optional[VarData] = dataclasses.field(default=None)
53
+
54
+ def __hash__(self) -> int:
55
+ """Define a hash function for a var.
56
+
57
+ Returns:
58
+ The hash of the var.
59
+ """
60
+ return hash(
61
+ (self._var_name, str(self._var_type), self._getter_name, self._setter_name)
62
+ )
63
+
64
+ @classmethod
65
+ def create(cls, var_name, default=None) -> "ClientStateVar":
66
+ """Create a local_state Var that can be accessed and updated on the client.
67
+
68
+ The `ClientStateVar` should be included in the highest parent component
69
+ that contains the components which will access and manipulate the client
70
+ state. It has no visual rendering, including it ensures that the
71
+ `useState` hook is called in the correct scope.
72
+
73
+ To render the var in a component, use the `value` property.
74
+
75
+ To update the var in a component, use the `set` property.
76
+
77
+ To access the var in an event handler, use the `retrieve` method with
78
+ `callback` set to the event handler which should receive the value.
79
+
80
+ To update the var in an event handler, use the `push` method with the
81
+ value to update.
82
+
83
+ Args:
84
+ var_name: The name of the variable.
85
+ default: The default value of the variable.
86
+
87
+ Returns:
88
+ ClientStateVar
89
+ """
90
+ if default is None:
91
+ default_var = Var.create_safe("", _var_is_local=False, _var_is_string=False)
92
+ elif not isinstance(default, Var):
93
+ default_var = Var.create_safe(default)
94
+ else:
95
+ default_var = default
96
+ setter_name = f"set{var_name.capitalize()}"
97
+ return cls(
98
+ _var_name="",
99
+ _setter_name=setter_name,
100
+ _getter_name=var_name,
101
+ _var_is_local=False,
102
+ _var_is_string=False,
103
+ _var_type=default_var._var_type,
104
+ _var_data=VarData.merge(
105
+ default_var._var_data,
106
+ VarData( # type: ignore
107
+ hooks={
108
+ f"const [{var_name}, {setter_name}] = useState({default_var._var_name_unwrapped})": None,
109
+ f"{_client_state_ref(var_name)} = {var_name}": None,
110
+ f"{_client_state_ref(setter_name)} = {setter_name}": None,
111
+ },
112
+ imports={
113
+ "react": [ImportVar(tag="useState", install=False)],
114
+ f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
115
+ },
116
+ ),
117
+ ),
118
+ )
119
+
120
+ @property
121
+ def value(self) -> Var:
122
+ """Get a placeholder for the Var.
123
+
124
+ This property can only be rendered on the frontend.
125
+
126
+ To access the value in a backend event handler, see `retrieve`.
127
+
128
+ Returns:
129
+ an accessor for the client state variable.
130
+ """
131
+ return (
132
+ Var.create_safe(
133
+ _client_state_ref(self._getter_name),
134
+ _var_is_local=False,
135
+ _var_is_string=False,
136
+ )
137
+ .to(self._var_type)
138
+ ._replace(
139
+ merge_var_data=VarData( # type: ignore
140
+ imports={
141
+ f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
142
+ }
143
+ )
144
+ )
145
+ )
146
+
147
+ @property
148
+ def set(self) -> Var:
149
+ """Set the value of the client state variable.
150
+
151
+ This property can only be attached to a frontend event trigger.
152
+
153
+ To set a value from a backend event handler, see `push`.
154
+
155
+ Returns:
156
+ A special EventChain Var which will set the value when triggered.
157
+ """
158
+ return (
159
+ Var.create_safe(
160
+ _client_state_ref(self._setter_name),
161
+ _var_is_local=False,
162
+ _var_is_string=False,
163
+ )
164
+ .to(EventChain)
165
+ ._replace(
166
+ merge_var_data=VarData( # type: ignore
167
+ imports={
168
+ f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
169
+ }
170
+ )
171
+ )
172
+ )
173
+
174
+ def retrieve(self, callback: EventHandler | Callable | None = None) -> EventSpec:
175
+ """Pass the value of the client state variable to a backend EventHandler.
176
+
177
+ The event handler must `yield` or `return` the EventSpec to trigger the event.
178
+
179
+ Args:
180
+ callback: The callback to pass the value to.
181
+
182
+ Returns:
183
+ An EventSpec which will retrieve the value when triggered.
184
+ """
185
+ return call_script(_client_state_ref(self._getter_name), callback=callback)
186
+
187
+ def push(self, value: Any) -> EventSpec:
188
+ """Push a value to the client state variable from the backend.
189
+
190
+ The event handler must `yield` or `return` the EventSpec to trigger the event.
191
+
192
+ Args:
193
+ value: The value to update.
194
+
195
+ Returns:
196
+ An EventSpec which will push the value when triggered.
197
+ """
198
+ return call_script(f"{_client_state_ref(self._setter_name)}({value})")
reflex/state.py CHANGED
@@ -279,11 +279,13 @@ class EventHandlerSetVar(EventHandler):
279
279
 
280
280
  Raises:
281
281
  AttributeError: If the given Var name does not exist on the state.
282
- ValueError: If the given Var name is not a str
282
+ EventHandlerValueError: If the given Var name is not a str
283
283
  """
284
+ from reflex.utils.exceptions import EventHandlerValueError
285
+
284
286
  if args:
285
287
  if not isinstance(args[0], str):
286
- raise ValueError(
288
+ raise EventHandlerValueError(
287
289
  f"Var name must be passed as a string, got {args[0]!r}"
288
290
  )
289
291
  # Check that the requested Var setter exists on the State at compile time.
@@ -357,6 +359,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
357
359
  # Whether the state has ever been touched since instantiation.
358
360
  _was_touched: bool = False
359
361
 
362
+ # Whether this state class is a mixin and should not be instantiated.
363
+ _mixin: ClassVar[bool] = False
364
+
360
365
  # A special event handler for setting base vars.
361
366
  setvar: ClassVar[EventHandler]
362
367
 
@@ -380,10 +385,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
380
385
  **kwargs: The kwargs to pass to the Pydantic init method.
381
386
 
382
387
  Raises:
383
- RuntimeError: If the state is instantiated directly by end user.
388
+ ReflexRuntimeError: If the state is instantiated directly by end user.
384
389
  """
390
+ from reflex.utils.exceptions import ReflexRuntimeError
391
+
385
392
  if not _reflex_internal_init and not is_testing_env():
386
- raise RuntimeError(
393
+ raise ReflexRuntimeError(
387
394
  "State classes should not be instantiated directly in a Reflex app. "
388
395
  "See https://reflex.dev/docs/state/ for further information."
389
396
  )
@@ -424,23 +431,30 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
424
431
  """
425
432
  return [
426
433
  v
427
- for mixin in cls.__mro__
428
- if mixin is cls or not issubclass(mixin, (BaseState, ABC))
434
+ for mixin in cls._mixins() + [cls]
429
435
  for v in mixin.__dict__.values()
430
436
  if isinstance(v, ComputedVar)
431
437
  ]
432
438
 
433
439
  @classmethod
434
- def __init_subclass__(cls, **kwargs):
440
+ def __init_subclass__(cls, mixin: bool = False, **kwargs):
435
441
  """Do some magic for the subclass initialization.
436
442
 
437
443
  Args:
444
+ mixin: Whether the subclass is a mixin and should not be initialized.
438
445
  **kwargs: The kwargs to pass to the pydantic init_subclass method.
439
446
 
440
447
  Raises:
441
- ValueError: If a substate class shadows another.
448
+ StateValueError: If a substate class shadows another.
442
449
  """
450
+ from reflex.utils.exceptions import StateValueError
451
+
443
452
  super().__init_subclass__(**kwargs)
453
+
454
+ cls._mixin = mixin
455
+ if mixin:
456
+ return
457
+
444
458
  # Event handlers should not shadow builtin state methods.
445
459
  cls._check_overridden_methods()
446
460
  # Computed vars should not shadow builtin state props.
@@ -471,7 +485,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
471
485
  else:
472
486
  # During normal operation, subclasses cannot have the same name, even if they are
473
487
  # defined in different modules.
474
- raise ValueError(
488
+ raise StateValueError(
475
489
  f"The substate class '{cls.__name__}' has been defined multiple times. "
476
490
  "Shadowing substate classes is not allowed."
477
491
  )
@@ -536,7 +550,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
536
550
  for name, value in mixin.__dict__.items():
537
551
  if isinstance(value, ComputedVar):
538
552
  fget = cls._copy_fn(value.fget)
539
- newcv = ComputedVar(fget=fget, _var_name=value._var_name)
553
+ newcv = value._replace(fget=fget)
554
+ # cleanup refs to mixin cls in var_data
555
+ newcv._var_data = None
540
556
  newcv._var_set_state(cls)
541
557
  setattr(cls, name, newcv)
542
558
  cls.computed_vars[newcv._var_name] = newcv
@@ -612,8 +628,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
612
628
  return [
613
629
  mixin
614
630
  for mixin in cls.__mro__
615
- if not issubclass(mixin, (BaseState, ABC))
616
- and mixin not in [pydantic.BaseModel, Base]
631
+ if (
632
+ mixin not in [pydantic.BaseModel, Base, cls]
633
+ and issubclass(mixin, BaseState)
634
+ and mixin._mixin is True
635
+ )
617
636
  ]
618
637
 
619
638
  @classmethod
@@ -736,7 +755,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
736
755
  parent_states = [
737
756
  base
738
757
  for base in cls.__bases__
739
- if types._issubclass(base, BaseState) and base is not BaseState
758
+ if issubclass(base, BaseState) and base is not BaseState and not base._mixin
740
759
  ]
741
760
  assert len(parent_states) < 2, "Only one parent state is allowed."
742
761
  return parent_states[0] if len(parent_states) == 1 else None # type: ignore
@@ -829,10 +848,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
829
848
  prop: The variable to initialize
830
849
 
831
850
  Raises:
832
- TypeError: if the variable has an incorrect type
851
+ VarTypeError: if the variable has an incorrect type
833
852
  """
853
+ from reflex.utils.exceptions import VarTypeError
854
+
834
855
  if not types.is_valid_var_type(prop._var_type):
835
- raise TypeError(
856
+ raise VarTypeError(
836
857
  "State vars must be primitive Python types, "
837
858
  "Plotly figures, Pandas dataframes, "
838
859
  "or subclasses of rx.Base. "
@@ -1454,6 +1475,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1454
1475
  Yields:
1455
1476
  StateUpdate object
1456
1477
  """
1478
+ from reflex.utils import telemetry
1479
+ from reflex.utils.exceptions import ReflexError
1480
+
1457
1481
  # Get the function to process the event.
1458
1482
  fn = functools.partial(handler.fn, state)
1459
1483
 
@@ -1489,9 +1513,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1489
1513
  yield state._as_state_update(handler, events, final=True)
1490
1514
 
1491
1515
  # If an error occurs, throw a window alert.
1492
- except Exception:
1516
+ except Exception as ex:
1493
1517
  error = traceback.format_exc()
1494
1518
  print(error)
1519
+ if isinstance(ex, ReflexError):
1520
+ telemetry.send("error", context="backend", detail=str(ex))
1495
1521
  yield state._as_state_update(
1496
1522
  handler,
1497
1523
  window_alert("An error occurred. See logs for details."),
@@ -1688,10 +1714,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1688
1714
  if initial:
1689
1715
  computed_vars = {
1690
1716
  # Include initial computed vars.
1691
- prop_name: cv._initial_value
1692
- if isinstance(cv, ComputedVar)
1693
- and not isinstance(cv._initial_value, types.Unset)
1694
- else self.get_value(getattr(self, prop_name))
1717
+ prop_name: (
1718
+ cv._initial_value
1719
+ if isinstance(cv, ComputedVar)
1720
+ and not isinstance(cv._initial_value, types.Unset)
1721
+ else self.get_value(getattr(self, prop_name))
1722
+ )
1695
1723
  for prop_name, cv in self.computed_vars.items()
1696
1724
  }
1697
1725
  elif include_computed:
@@ -1818,7 +1846,7 @@ class OnLoadInternalState(State):
1818
1846
  ]
1819
1847
 
1820
1848
 
1821
- class ComponentState(Base):
1849
+ class ComponentState(State, mixin=True):
1822
1850
  """Base class to allow for the creation of a state instance per component.
1823
1851
 
1824
1852
  This allows for the bundling of UI and state logic into a single class,
@@ -1860,6 +1888,18 @@ class ComponentState(Base):
1860
1888
  # The number of components created from this class.
1861
1889
  _per_component_state_instance_count: ClassVar[int] = 0
1862
1890
 
1891
+ @classmethod
1892
+ def __init_subclass__(cls, mixin: bool = False, **kwargs):
1893
+ """Overwrite mixin default to True.
1894
+
1895
+ Args:
1896
+ mixin: Whether the subclass is a mixin and should not be initialized.
1897
+ **kwargs: The kwargs to pass to the pydantic init_subclass method.
1898
+ """
1899
+ if ComponentState in cls.__bases__:
1900
+ mixin = True
1901
+ super().__init_subclass__(mixin=mixin, **kwargs)
1902
+
1863
1903
  @classmethod
1864
1904
  def get_component(cls, *children, **props) -> "Component":
1865
1905
  """Get the component instance.
reflex/style.py CHANGED
@@ -16,10 +16,10 @@ LIGHT_COLOR_MODE: str = "light"
16
16
  DARK_COLOR_MODE: str = "dark"
17
17
 
18
18
  # Reference the global ColorModeContext
19
- color_mode_var_data = VarData( # type: ignore
19
+ color_mode_var_data = VarData(
20
20
  imports={
21
- f"/{constants.Dirs.CONTEXTS_PATH}": {ImportVar(tag="ColorModeContext")},
22
- "react": {ImportVar(tag="useContext")},
21
+ f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
22
+ "react": [ImportVar(tag="useContext")],
23
23
  },
24
24
  hooks={
25
25
  f"const [ {constants.ColorMode.NAME}, {constants.ColorMode.TOGGLE} ] = useContext(ColorModeContext)": None,