diff --git a/packages/reflex-base/src/reflex_base/.templates/web/utils/state.js b/packages/reflex-base/src/reflex_base/.templates/web/utils/state.js index ce4a19a4747..c07b4dfcbac 100644 --- a/packages/reflex-base/src/reflex_base/.templates/web/utils/state.js +++ b/packages/reflex-base/src/reflex_base/.templates/web/utils/state.js @@ -1142,6 +1142,28 @@ export const isNotNullOrUndefined = (val) => { return (val ?? undefined) !== undefined; }; +/*** + * Python-semantics OR: returns `a` if python-truthy, else evaluates and returns `b`. + * `b` is a thunk so it is only evaluated when needed, preserving short-circuit. + * @template A + * @template B + * @param {A} a The left-hand value. + * @param {() => B} b Thunk producing the right-hand value. + * @returns {A | B} `a` if python-truthy, otherwise the result of `b()`. + */ +export const pyOr = (a, b) => (isTrue(a) ? a : b()); + +/*** + * Python-semantics AND: returns `a` if python-falsy, else evaluates and returns `b`. + * `b` is a thunk so it is only evaluated when needed, preserving short-circuit. + * @template A + * @template B + * @param {A} a The left-hand value. + * @param {() => B} b Thunk producing the right-hand value. + * @returns {A | B} `a` if python-falsy, otherwise the result of `b()`. + */ +export const pyAnd = (a, b) => (isTrue(a) ? b() : a); + /** * Get the value from a ref. * @param ref The ref to get the value from. diff --git a/packages/reflex-base/src/reflex_base/vars/base.py b/packages/reflex-base/src/reflex_base/vars/base.py index 4034b69aa3c..0181b2774ea 100644 --- a/packages/reflex-base/src/reflex_base/vars/base.py +++ b/packages/reflex-base/src/reflex_base/vars/base.py @@ -1955,6 +1955,15 @@ def __hash__(self: DataclassInstance) -> int: )) +_PY_AND_IMPORT: ImportDict = { + f"$/{constants.Dirs.STATE_PATH}": [ImportVar(tag="pyAnd")], +} + +_PY_OR_IMPORT: ImportDict = { + f"$/{constants.Dirs.STATE_PATH}": [ImportVar(tag="pyOr")], +} + + def and_operation( a: Var[VAR_TYPE] | Any, b: Var[OTHER_VAR_TYPE] | Any ) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: @@ -1982,8 +1991,9 @@ def _and_operation(a: Var, b: Var): The result of the logical AND operation. """ return var_operation_return( - js_expression=f"({a} && {b})", + js_expression=f"pyAnd({a}, () => ({b}))", var_type=unionize(a._var_type, b._var_type), + var_data=VarData(imports=_PY_AND_IMPORT), ) @@ -2014,8 +2024,9 @@ def _or_operation(a: Var, b: Var): The result of the logical OR operation. """ return var_operation_return( - js_expression=f"({a} || {b})", + js_expression=f"pyOr({a}, () => ({b}))", var_type=unionize(a._var_type, b._var_type), + var_data=VarData(imports=_PY_OR_IMPORT), ) diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 72aeb7ff78f..6971cdffcd4 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -313,8 +313,8 @@ def test_basic_operations(TestObj): assert str(LiteralNumberVar.create(1) // 2) == "Math.floor(1 / 2)" assert str(LiteralNumberVar.create(1) % 2) == "(1 % 2)" assert str(LiteralNumberVar.create(1) ** 2) == "(1 ** 2)" - assert str(LiteralNumberVar.create(1) & v(2)) == "(1 && 2)" - assert str(LiteralNumberVar.create(1) | v(2)) == "(1 || 2)" + assert str(LiteralNumberVar.create(1) & v(2)) == "pyAnd(1, () => (2))" + assert str(LiteralNumberVar.create(1) | v(2)) == "pyOr(1, () => (2))" assert str(LiteralArrayVar.create([1, 2, 3])[0]) == "[1, 2, 3]?.at?.(0)" assert ( str(LiteralObjectVar.create({"a": 1, "b": 2})["a"]) @@ -1025,7 +1025,7 @@ def test_all_number_operations(): assert ( str(even_more_complicated_number) - == "!(isTrue((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))))))" + == "!(isTrue(pyOr(Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))), () => (pyAnd(2, () => (Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))))))))" ) assert str(LiteralNumberVar.create(5) > False) == "(5 > 0)"