Anvil-reactive: Add reactivity to your Anvil apps

If you like data bindings and wish you could have more power like that in code,
then this library might be for you.

It’s based on a signals pattern, common in many front-end frameworks.
The implementation we wrote is based on solid-js,
one of the fastest javascript frameworks out there.
And is the next step in the evolution of anvil_labs.atomic

Example usage

from anvil_reactive.main import signal, render_effect

class Counter1(Counter1Template):
    counter = signal(0)
    
    @render_effect
    def update_text(self):
        self.counter_label.text = self.counter

    def plus_button_click(self, **event_args):
        self.counter += 1

    def minus_button_click(self, **event_args):
        self.counter -= 1

When the component is added to the screen:

  • all render effects run
  • in this example the update_text() method reads from self.counter which is a signal
  • later, when the value of this signal changes, render_effects that depend on the signal will re-run.
  • These effects are batched to create performant updates.

Why?

It’s mostly to explore new patterns in anvil apps, and it’s quite a fun way to program.
You probably won’t want to use it in production, but we’ve created tagged versions so you can safely depend on a version without breakages.

It’s available as a third party dependency: N7KFE4YBWMGWJ5OX
Or by cloning from the github repo: GitHub - anvilistas/reactive: signals for anvil


Examples

Here is a set of examples you can clone:

Code Examples
from anvil_reactive.main import reactive_class, render_effect

@reactive_class
class Counter2(Counter2Template):
    def __init__(self, **properties):
        self.counter = 0 # all attributes in a reactive class are signals
    
    @render_effect
    def update_text(self):
        self.counter_label.text = self.counter

    def plus_button_click(self, **event_args):
        self.counter += 1

    def minus_button_click(self, **event_args):
        self.counter -= 1
from anvil_reactive.main import bind, reactive_class

@reactive_class
class Counter3(Counter3Template):
    def __init__(self, **properties):
        self.counter = 0
        bind(self.counter_label, "text", lambda: self.counter)

    def plus_button_click(self, **event_args):
        self.counter += 1

    def minus_button_click(self, **event_args):
        self.counter -= 1

from anvil.tables import app_tables
from anvil_reactive.main import render_effect, reactive_instance

counter_row = reactive_instance(app_tables.counter.get()) # 🤯

class Counter4(Counter4Template):
    @render_effect
    def update_text(self):
        self.counter_label.text = counter_row["value"]

    def plus_button_click(self, **event_args):
        anvil.server.call("update_counter", counter_row, 1)

    def minus_button_click(self, **event_args):
        anvil.server.call("update_counter", counter_row, -1)


For questions, comments, suggestions, feel free to ask over at the github repo:

12 Likes

This looks great!

Just one question;

What does the reactive_class decorator do? It is in the 3rd counter example, but other than that it’s the same as the first example with no difference I can see in functionality with all the methods for reactivity in the first example being in the third.

When you use the reactive_class decorator, every attribute of an instance of that class is implicitly a signal. [1]

So any time you get and set an attribute you are reading and writing to signals.

I like the first example for its explicitness about which parts are reactive. But the reactive class decorator is very convenient.

There’s another example in the clone where we create a todo_store that is a reactive_class. It’s a nice example of an instance where I’d want every attribute to be reactive. It has an attribute todos, which is a list of dicts, and changing the todos attribute causes the ui to update.

[1] There’s some under the hood magic that makes this happen. For more details might be worth moving to the discussions page