Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into lendemor/use_toast_fo…
Browse files Browse the repository at this point in the history
…r_connection_error
  • Loading branch information
masenf committed May 16, 2024
2 parents f781e24 + 99d5910 commit adb0c35
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 23 deletions.
9 changes: 7 additions & 2 deletions reflex/.templates/web/utils/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,13 @@ export const applyDelta = (state, delta) => {
export const applyEvent = async (event, socket) => {
// Handle special events
if (event.name == "_redirect") {
if (event.payload.external) window.open(event.payload.path, "_blank");
else Router.push(event.payload.path);
if (event.payload.external) {
window.open(event.payload.path, "_blank");
} else if (event.payload.replace) {
Router.replace(event.payload.path);
} else {
Router.push(event.payload.path);
}
return false;
}

Expand Down
11 changes: 11 additions & 0 deletions reflex/components/core/foreach.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from reflex.components.component import Component
from reflex.components.tags import IterTag
from reflex.constants import MemoizationMode
from reflex.state import ComponentState
from reflex.utils import console
from reflex.vars import Var

Expand Down Expand Up @@ -50,6 +51,7 @@ def create(
Raises:
ForeachVarError: If the iterable is of type Any.
TypeError: If the render function is a ComponentState.
"""
if props:
console.deprecate(
Expand All @@ -65,6 +67,15 @@ def create(
"(If you are trying to foreach over a state var, add a type annotation to the var). "
"See https://reflex.dev/docs/library/layout/foreach/"
)

if (
hasattr(render_fn, "__qualname__")
and render_fn.__qualname__ == ComponentState.create.__qualname__
):
raise TypeError(
"Using a ComponentState as `render_fn` inside `rx.foreach` is not supported yet."
)

component = cls(
iterable=iterable,
render_fn=render_fn,
Expand Down
13 changes: 11 additions & 2 deletions reflex/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,18 +467,27 @@ def fn():
)


def redirect(path: str | Var[str], external: Optional[bool] = False) -> EventSpec:
def redirect(
path: str | Var[str],
external: Optional[bool] = False,
replace: Optional[bool] = False,
) -> EventSpec:
"""Redirect to a new path.
Args:
path: The path to redirect to.
external: Whether to open in new tab or not.
replace: If True, the current page will not create a new history entry.
Returns:
An event to redirect to the path.
"""
return server_side(
"_redirect", get_fn_signature(redirect), path=path, external=external
"_redirect",
get_fn_signature(redirect),
path=path,
external=external,
replace=replace,
)


Expand Down
2 changes: 2 additions & 0 deletions reflex/experimental/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from ..utils.console import warn
from . import hooks as hooks
from .client_state import ClientStateVar as ClientStateVar
from .layout import layout as layout
from .misc import run_in_thread as run_in_thread

Expand All @@ -16,6 +17,7 @@
)

_x = SimpleNamespace(
client_state=ClientStateVar.create,
hooks=hooks,
layout=layout,
progress=progress,
Expand Down
198 changes: 198 additions & 0 deletions reflex/experimental/client_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"""Handle client side state with `useState`."""

import dataclasses
import sys
from typing import Any, Callable, Optional, Type

from reflex import constants
from reflex.event import EventChain, EventHandler, EventSpec, call_script
from reflex.utils.imports import ImportVar
from reflex.vars import Var, VarData


def _client_state_ref(var_name: str) -> str:
"""Get the ref path for a ClientStateVar.
Args:
var_name: The name of the variable.
Returns:
An accessor for ClientStateVar ref as a string.
"""
return f"refs['_client_state_{var_name}']"


@dataclasses.dataclass(
eq=False,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ClientStateVar(Var):
"""A Var that exists on the client via useState."""

# The name of the var.
_var_name: str = dataclasses.field()

# Track the names of the getters and setters
_setter_name: str = dataclasses.field()
_getter_name: str = dataclasses.field()

# The type of the var.
_var_type: Type = dataclasses.field(default=Any)

# Whether this is a local javascript variable.
_var_is_local: bool = dataclasses.field(default=False)

# Whether the var is a string literal.
_var_is_string: bool = dataclasses.field(default=False)

# _var_full_name should be prefixed with _var_state
_var_full_name_needs_state_prefix: bool = dataclasses.field(default=False)

# Extra metadata associated with the Var
_var_data: Optional[VarData] = dataclasses.field(default=None)

def __hash__(self) -> int:
"""Define a hash function for a var.
Returns:
The hash of the var.
"""
return hash(
(self._var_name, str(self._var_type), self._getter_name, self._setter_name)
)

@classmethod
def create(cls, var_name, default=None) -> "ClientStateVar":
"""Create a local_state Var that can be accessed and updated on the client.
The `ClientStateVar` should be included in the highest parent component
that contains the components which will access and manipulate the client
state. It has no visual rendering, including it ensures that the
`useState` hook is called in the correct scope.
To render the var in a component, use the `value` property.
To update the var in a component, use the `set` property.
To access the var in an event handler, use the `retrieve` method with
`callback` set to the event handler which should receive the value.
To update the var in an event handler, use the `push` method with the
value to update.
Args:
var_name: The name of the variable.
default: The default value of the variable.
Returns:
ClientStateVar
"""
if default is None:
default_var = Var.create_safe("", _var_is_local=False, _var_is_string=False)
elif not isinstance(default, Var):
default_var = Var.create_safe(default)
else:
default_var = default
setter_name = f"set{var_name.capitalize()}"
return cls(
_var_name="",
_setter_name=setter_name,
_getter_name=var_name,
_var_is_local=False,
_var_is_string=False,
_var_type=default_var._var_type,
_var_data=VarData.merge(
default_var._var_data,
VarData( # type: ignore
hooks={
f"const [{var_name}, {setter_name}] = useState({default_var._var_name_unwrapped})": None,
f"{_client_state_ref(var_name)} = {var_name}": None,
f"{_client_state_ref(setter_name)} = {setter_name}": None,
},
imports={
"react": {ImportVar(tag="useState", install=False)},
f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
},
),
),
)

@property
def value(self) -> Var:
"""Get a placeholder for the Var.
This property can only be rendered on the frontend.
To access the value in a backend event handler, see `retrieve`.
Returns:
an accessor for the client state variable.
"""
return (
Var.create_safe(
_client_state_ref(self._getter_name),
_var_is_local=False,
_var_is_string=False,
)
.to(self._var_type)
._replace(
merge_var_data=VarData( # type: ignore
imports={
f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
}
)
)
)

@property
def set(self) -> Var:
"""Set the value of the client state variable.
This property can only be attached to a frontend event trigger.
To set a value from a backend event handler, see `push`.
Returns:
A special EventChain Var which will set the value when triggered.
"""
return (
Var.create_safe(
_client_state_ref(self._setter_name),
_var_is_local=False,
_var_is_string=False,
)
.to(EventChain)
._replace(
merge_var_data=VarData( # type: ignore
imports={
f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
}
)
)
)

def retrieve(self, callback: EventHandler | Callable | None = None) -> EventSpec:
"""Pass the value of the client state variable to a backend EventHandler.
The event handler must `yield` or `return` the EventSpec to trigger the event.
Args:
callback: The callback to pass the value to.
Returns:
An EventSpec which will retrieve the value when triggered.
"""
return call_script(_client_state_ref(self._getter_name), callback=callback)

def push(self, value: Any) -> EventSpec:
"""Push a value to the client state variable from the backend.
The event handler must `yield` or `return` the EventSpec to trigger the event.
Args:
value: The value to update.
Returns:
An EventSpec which will push the value when triggered.
"""
return call_script(f"{_client_state_ref(self._setter_name)}({value})")
4 changes: 3 additions & 1 deletion reflex/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,9 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs):
for name, value in mixin.__dict__.items():
if isinstance(value, ComputedVar):
fget = cls._copy_fn(value.fget)
newcv = ComputedVar(fget=fget, _var_name=value._var_name)
newcv = value._replace(fget=fget)
# cleanup refs to mixin cls in var_data
newcv._var_data = None
newcv._var_set_state(cls)
setattr(cls, name, newcv)
cls.computed_vars[newcv._var_name] = newcv
Expand Down
2 changes: 1 addition & 1 deletion reflex/utils/prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ def initialize_gitignore(
# Write files to the .gitignore file.
with open(gitignore_file, "w", newline="\n") as f:
console.debug(f"Creating {gitignore_file}")
f.write(f"{(path_ops.join(sorted(files_to_ignore))).lstrip()}")
f.write(f"{(path_ops.join(sorted(files_to_ignore))).lstrip()}\n")


def initialize_requirements_txt():
Expand Down
16 changes: 16 additions & 0 deletions reflex/utils/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@
from reflex.base import Base
from reflex.utils import console, serializers

if sys.version_info >= (3, 12):
from typing import override
else:

def override(func: Callable) -> Callable:
"""Fallback for @override decorator.
Args:
func: The function to decorate.
Returns:
The unmodified function.
"""
return func


# Potential GenericAlias types for isinstance checks.
GenericAliasTypes = [_GenericAlias]

Expand Down
Loading

0 comments on commit adb0c35

Please sign in to comment.