From 48cd31584c41c37612eb7d6884e95139a03aa282 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Thu, 22 Jun 2023 10:04:49 +0200 Subject: [PATCH] fix: use_reactive did not use a newly passed reactive variable instead, if would still use the first one passed, and only assign the new values to it, but the linking would not be bi-directional. Now we simply return the reactive variable passed. --- solara/hooks/misc.py | 40 ++++++++++++++++++++------------- tests/unit/lab/toestand_test.py | 28 +++++++++++++++++++++++ 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/solara/hooks/misc.py b/solara/hooks/misc.py index 10e9d4f69..d71d9b977 100644 --- a/solara/hooks/misc.py +++ b/solara/hooks/misc.py @@ -405,31 +405,34 @@ def assign(): def use_reactive( - initial_value: Union[T, solara.Reactive[T]], + value: Union[T, solara.Reactive[T]], on_change: Optional[Callable[[T], None]] = None, ) -> solara.Reactive[T]: - """ - Ensures that the returned value is a `Reactive` object. + """Creates a reactive variable with the a local component scope. + It is a useful alternative to `use_state` when you want to use a + reactive variable for the component state. See also [our documentation on state management](/docs/fundamentals/state-management). - This hook is useful for implementing components that accept either a - `Reactive` object or a normal value along with an optional `on_change` - callback. It can also be used as an alternative to `use_state` when you - want to use a `Reactive` object as the component state. + If the variable passed is a reactive variable, it will be returned instead and no + new reactive variable will be created. This is useful for implementing component + that accept either a reactive variable or a normal value along with an optional `on_change` + callback. ## Arguments: - * initial_value (Union[T, solara.Reactive[T]]): The initial value of the - reactive variable. If a `Reactive` object is provided, it will be - used directly. Otherwise, a new `Reactive` object will be created - with the provided initial value. + * value (Union[T, solara.Reactive[T]]): The value of the + reactive variable. If a reactive variable is provided, it will be + used directly. Otherwise, a new reactive variable will be created + with the provided initial value. If the argument passed changes + the reactive variable will be updated. + * on_change (Optional[Callable[[T], None]]): An optional callback function that will be called when the reactive variable's value changes. Returns: - solara.Reactive[T]: A `Reactive` object with the specified initial value - or the provided `Reactive` object. + solara.Reactive[T]: A reactive variable with the specified initial value + or the provided reactive variable. ## Examples @@ -474,9 +477,13 @@ def MyComponent(value: Union[T, solara.Reactive[T]], on_change_ref.current = on_change def create(): - return solara.reactive(initial_value) if not isinstance(initial_value, solara.Reactive) else initial_value + if not isinstance(value, solara.Reactive): + return solara.reactive(value) reactive_value = solara.use_memo(create, dependencies=[]) + if isinstance(value, solara.Reactive): + reactive_value = value + assert reactive_value is not None updating = solara.use_ref(False) def forward_on_change(): @@ -489,11 +496,12 @@ def forward(value): def update(): updating.current = True try: - reactive_value.value = initial_value if not isinstance(initial_value, solara.Reactive) else initial_value.value + if not isinstance(value, solara.Reactive): + reactive_value.value = value finally: updating.current = False - solara.use_memo(update, [initial_value]) + solara.use_memo(update, [value]) solara.use_effect(forward_on_change, []) return reactive_value diff --git a/tests/unit/lab/toestand_test.py b/tests/unit/lab/toestand_test.py index 0a3a413a4..77bc03bd3 100644 --- a/tests/unit/lab/toestand_test.py +++ b/tests/unit/lab/toestand_test.py @@ -965,3 +965,31 @@ class Foo: s = repr(bears.fields.count) assert s.startswith("") + + +def test_use_reactive_update(): + control = Reactive(0) + var1 = Reactive(1) + var2 = Reactive(2) + + @solara.component + def Test(): + var: Reactive[int] + if control.value == 0: + var = solara.use_reactive(var1) + else: + var = solara.use_reactive(var2) + + return solara.IntSlider("test: " + str(var.value), value=var) + + box, rc = solara.render(Test(), handle_error=False) + assert rc.find(v.Slider).widget.v_model == 1 + assert rc.find(v.Slider).widget.label == "test: 1" + control.value = 1 + assert rc.find(v.Slider).widget.v_model == 2 + assert rc.find(v.Slider).widget.label == "test: 2" + # the slider should be using the same reactive variable (var2) + rc.find(v.Slider).widget.v_model = 1 + assert var2.value == 1 + assert rc.find(v.Slider).widget.label == "test: 1" + rc.close()