Skip to content

Commit

Permalink
fix: use_reactive did not use a newly passed reactive variable
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
maartenbreddels committed Jun 22, 2023
1 parent 42719e3 commit 48cd315
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 16 deletions.
40 changes: 24 additions & 16 deletions solara/hooks/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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():
Expand All @@ -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
28 changes: 28 additions & 0 deletions tests/unit/lab/toestand_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,3 +965,31 @@ class Foo:
s = repr(bears.fields.count)
assert s.startswith("<Field <Reactive value=Bears(type='brown', count=1)")
assert s.endswith(".count>")


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()

0 comments on commit 48cd315

Please sign in to comment.