Reactivity and effects

Modern terms definition:

reactive

In web frameworks, reactivity is the system that automatically updates the webpage (the UI) whenever the underlying data (the state) changes.

imperative

The opposite model to reactivity is the imperative model (often referred to as manual DOM manipulation in web development).

effects

In reactive web frameworks, effects (often called useEffect in React or watchEffect in Vue) are functions that run automatically when the data they depend on changes. They are used to sync your reactive data with systems outside the framework.

Fortunately, pantra supports both models: reactive and imperative. Second one by default.

Note

Despite imperative model is older at takes more code to manage, it makes logic much more explicitly obvious and code running faster, avoiding implicit unwanted effects.

Example of imperative logic:

<div ref:message>Nothing</div>
<button on:click="action">Click me</button>
<python>
def action(node):
    refs['message'].set_text("Hello there!")
</python>

Let’s imaging we want to keep message in local context:

<div ref:message>{{message}}</div>
<button on:click="action">Click me</button>
<python>
message: str = "Nothing"
def action(node):
    ctx['message'] = "Hello there!"
    refs['message'].update()
</python>

And this code could be simplified:

<div>!{{message}}</div>
<button on:click="action">Click me</button>
<python>
message: str = "Nothing"
def action(node):
    ctx['message'] = "Hello there!"
</python>

Noticed exclamation mark (!) here? It informs engine to watch for message variable changes and then update related node (<div> element).

It works the same for attributes expressions

<div style="width: 100vw; height: 100vh;">
    <p on:drag="DragCtrl"
       style="
            width: 50px;
            height: 50px;
            position: absolute;
            background-color: blue;"
       css:left=!{x}
    />
    <button on:click="action">Click me</button>
</div>
<python>
from pantra.ctx import *

x: int = 0
def action(node):
    ctx['x'] = WebUnits(100)
</python>

And for conditional nodes:

!{{#if mode == "entry"}}
    <input type="text" placeholder="User name" bind:value="user_name">
    <button on:click="action">Enter</button>
{{#else}}
    <div>Hello, {{user_name}}!</div>
{{/if}}
<python>
mode: str = "entry"
user_name: str = ""
def action(node):
    ctx["mode"] = "passed"
</python>

See also

Read more about reactive macros, reactive attribute and reactive tag.

Note

Reactive variable names are extracted from the expression using ast parser. It means there is no function call tracing for deeper variables changes. As example:

<div>!{{get_message()}}</div>
<python>
message: str = "Hello"
def get_message():
    return message # <- variable "message" will no be tracked for changes
</python>

This was done consciously, to avoid possible mistakes from implicit variable changes tracking and further effects. Better practise is to make reactions explicitly:

<div ref:message_slot>{{get_message()}}</div>
<react to="message" action="update:message_slot"/>
<python>
message: str = "Hello"
def get_message():
    return message
</python>

Reed more about node events.

Reactive dictionary

class pantra.components.reactdict.ReactDict[source]

Extended dictionary with reactions the changes

react_vars

variable to nodes registered connections

Type:

dict[str, set[RenderNode]]

react_nodes

set of all reactive nodes for fast search

Type:

set[RenderNode]

call(func_name, *args, **kwargs)[source]

get function by name and call with args

register_reactions(var_names: set[str], node: RenderNode)[source]

register variable to nodes reaction

Parameters:
remove_reactions_to(node: RenderNode)[source]

remove all reactions from node

Parameters:

node (RenderNode)

has_reactions_to(node: RenderNode) bool[source]

check if node has registered reactions

Parameters:

node (RenderNode)

Return type:

bool

set_quietly(key, value)[source]

set variable without effects