Giter Site home page Giter Site logo

widgetti / reacton Goto Github PK

View Code? Open in Web Editor NEW
290.0 5.0 19.0 875 KB

A pure Python port of React for ipywidgets

Home Page: https://reacton.solara.dev/

License: MIT License

Python 96.65% Makefile 0.15% Batchfile 0.11% Shell 0.03% Jupyter Notebook 3.07%
ipywidgets jupyter python react user-interface

reacton's People

Contributors

12rambau avatar iisakkirotko avatar jhsmit avatar jtpio avatar maartenbreddels avatar mariobuikhuizen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

reacton's Issues

Simple example of reacton as a class

Hi, I feel this project should really help me but I am struggling to get how it would be beneficial compared to a class type structure. Also, would there be a benefit to using Reacton within a class structure? I couldn't get that to work. I would very much appreciate your thoughts on the following button click examples below. In particular, how can Reacton work in classes? and would that structure typically be used in large Jupyter widget web-app projects?

Old school method (taken from Reacton docs)

import ipywidgets as widgets

clicks = 0  # issue 3
def on_click(button):
    global clicks  # issue 3
    clicks += 1
    button.description = f"Clicked {clicks} times"     
button = widgets.Button(description="Clicked 0 times") 
button.on_click(on_click)  #
button

Reacton (taken from Reacton docs)

import reacton

@reacton.component
def ButtonClick():
    # first render, this return 0, after that, the last argument
    # of set_clicks
    clicks, set_clicks = reacton.use_state(0)
    
    def my_click_handler():
        # trigger a new render with a new value for clicks
        set_clicks(clicks+1)

    button = w.Button(description=f"Clicked {clicks} times",
                      on_click=my_click_handler)
    return button

ButtonClick()

Old school method BUT in a class

The method I currently use for large projects.

import ipywidgets as widgets

class ButtonClick:
    def __init__(self):
        self.clicks = -1  # issue 3
        self.button = widgets.Button() 
        self.button.on_click(self.on_click)  
        self.on_click(_)

    def on_click(self,_):
        self.clicks += 1
        self.button.description = f"Clicked {self.clicks} times"     
ButtonClick().button

My attempt using Reacton in a class but it doesn't work.

Is this even needed? I'm not sure. It would be nice to incorporate Reacton in existing code within a class based structure.

class ButtonClick_r:
    def __init__(self):
        self.ButtonClick()
        self.clicks= 0
        self.button = w.Button(description=f"Clicked {self.clicks} times",
                          on_click=self.my_click_handler)        
    @reacton.component
    def ButtonClick(self):
        # first render, this return 0, after that, the last argument
        # of set_clicks
        self.clicks, set_clicks = reacton.use_state(0)

    def my_click_handler(self):
        # trigger a new render with a new value for clicks
        set_clicks(self.clicks+1)

ButtonClick_r().button

minor correction in docs

Started exploring the project, no react knowledge, I like the pure python approach.
Just a minor thing:
In the getting-started.md the example says:

el = ButtonClick
display(el)

I guess this was meant "()":

el = ButtonClick()
display(el)

Thanks a ton.

Compile-time detection of `@component` functions with inconsistent state

Consider the following simple component:

import solara as sl

@sl.component
def Page():
    initial = sl.use_reactive(1)

    def make_invalid():
        initial.set(-initial.value)

    sl.Button("Toggle invalid", on_click=make_invalid)

    if initial.value < 0:
        sl.Markdown("Invalid value")
        return
    another_reactive = sl.use_reactive(0)

After clicking the button, we get the following error:

Traceback (most recent call last):
  File "/Users/ntjess/miniconda3/envs/py312/lib/python3.12/site-packages/reacton/core.py", line 1751, in _render
    raise RuntimeError(
RuntimeError: Previously render had 4 effects, this run 3 (in element/component: Page()/react.component(__main__.Page)). Are you using conditional hooks?

In this case, it is clear how to fix the issue. But if the condition to trigger conditional hooks rarely appears, debugging is especially difficult.

I wrote the following function which attempts to detect these cases at compile time:

DEFAULT_USE_FUNCTIONS = (
    "use_state",
    "use_reactive",
    "use_thread",
    "use_task",
    "use_effect",
    "use_memo",
)
def error_on_early_return(component: t.Callable, use_functions=DEFAULT_USE_FUNCTIONS):
    nodes = list(ast.walk(ast.parse(inspect.getsource(component))))
    earliest_return_node: ast.Return | None = None
    latest_use_node: ast.expr | None = None
    latest_use_node_id = ""
    for node in nodes:
        if isinstance(node, ast.Return):
            if (
                earliest_return_node is None
                or node.lineno > earliest_return_node.lineno
            ):
                earliest_return_node = node
        elif isinstance(node, ast.Call):
            func = node.func
            if isinstance(func, ast.Call):
                # Nested function, it will appear in another node later
                continue
            if isinstance(func, ast.Name):
                id_ = func.id
            elif isinstance(func, ast.Attribute):
                id_ = func.attr
            else:
                raise ValueError(
                    f"Unexpected function node type: {func}, {func.lineno=}"
                )
            if id_ in use_functions and (
                latest_use_node is None or node.lineno > latest_use_node.lineno
            ):
                latest_use_node = node
                latest_use_node_id = id_
    if (
        earliest_return_node
        and latest_use_node
        and earliest_return_node.lineno <= latest_use_node.lineno
    ):
        raise ValueError(
            f"{component}: `{latest_use_node_id}` found on line {latest_use_node.lineno} despite early"
            f" return on line {earliest_return_node. lineno}"
        )

Running this on the sample component provided a much more helpful error (again, at compile time instead of after a conditional toggle!):

ValueError: <function Page at 0x100ebe200>: `use_reactive` found on line 13 despite early return on line 12

I am curious what other cases should be detected and whether you think this function can be adopted into reacton or solara more generally.

  • use_* inside an if or for block
  • return before use_*
  • use_* outside a render context (this would be a bit harder, but still possible by tracing ast.Call usage)

In my case, I wrapped solara.component to call this function first and found a few other conditional hook bugs in my existing components.

Matplotlib plots not rendering properly in output widgets.

When using an ipywidgets.Output widget in a reacton component, plots are not displayed in notebook. I've tested this with the ipympl and inline backends. It works fine when using ipywidgets directly, and used to work with react-ipywdgets 0.11.

Here's an example that shows both the working case (with ipywidgeds) and the non-working case (reacton).

import matplotlib.pyplot as plt
import reacton as react
import reacton.ipywidgets as w
import ipywidgets as widgets

#%matplotlib inline
%matplotlib ipympl

def plot(outputWidget):
    with plt.ioff():
        fig, ax = plt.subplots()

        fruits = ['apple', 'blueberry', 'cherry', 'orange']
        counts = [40, 100, 30, 55]
        bar_labels = ['red', 'blue', '_red', 'orange']
        bar_colors = ['tab:red', 'tab:blue', 'tab:red', 'tab:orange']

        ax.bar(fruits, counts, label=bar_labels, color=bar_colors)

        ax.set_ylabel('fruit supply')
        ax.set_title('Fruit supply by kind and color')
        ax.legend(title='Fruit color')

        with outputWidget:
            print("Displaying")
            plt.show()


# Reacton Test Case
@react.component()
def Test():
    render, setRender = react.use_state(False)
    
    def displayReacton():
        if render == False:
            return
        
        outputWidget = react.get_widget(output)
        plot(outputWidget)
    
    with w.VBox() as vbox:
        w.Button(description="Render", on_click=lambda: setRender(True))
        output = w.Output()
        
    react.use_effect(displayReacton, [render])
    
    return vbox

Test()

# Ipywidgets test case
outputWidget = widgets.Output()
button = widgets.Button(description="Render")

def displayIpywidgets(_):
    plot(outputWidget)

button.on_click(displayIpywidgets)

widgets.VBox([
    button,
    outputWidget
])

Software Versions:

python                          3.10.9
matplotlib-base           3.7.1
ipympl                           0.9.3
reacton                          1.2.2

Reacton is not installed in jupyterlite environment

I was trying to execute the calculator demo
https://reacton.solara.dev/en/latest/_output/lab/

and from import reacton I got the following error.

---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
/tmp/xpython_42/3272763547.py in <cell line: 1>()
----> 1 import reacton
      2 import reacton.ipywidgets as w
      3 
      4 
      5 @reacton.component

ModuleNotFoundError: No module named 'reacton'

Reacton does appear in this jupyterlite config file (I think)
https://github.com/widgetti/reacton/blob/master/docs/xeus-python-environment.yml

How to use VuetifyTemplate method?

I am trying to implement this very simple example with reacton.ipvuetify.VuetifyTemplate. The implementation if templates in reacton is a method instead of a class and I am unsure as to how you pass methods to the function (in this example the vue_btn_click).

import traitlets
import ipyvuetify

class TestWidget(ipyvuetify.VuetifyTemplate):
    template = traitlets.Unicode('''
        <template>
            <v-btn @click="btn_click">
                Button
            </v-btn>
        </template>
    ''').tag(sync=True)

    def vue_btn_click(self, data):
        print(1)

test = TestWidget()
test

Deprecated Warning Widget.widgets is deprecated

I got following warning when running pytest:
reacton\core.py:362: DeprecationWarning: Widget.widgets is deprecated.
before = set(widgets.Widget.widgets)
reacton\core.py:1936: DeprecationWarning: Widget.widgets is deprecated.
orphan_widgets = set([widgets.Widget.widgets[k] for k in orphan_ids])

All occurrences of referencing Widget.widgets throw DeprecationWarning.

In ipywidgets(8.0.6), it states:
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.

Anyway to avoid?

P.S.
What's the difference between render() and render_fixed()? I do see render add another container around elements. Any explanations about when to choose which?

Thanks a lot.

Qt

Like many other people, I want to write good-looking apps with React-style (Model-View-Update, The Elm Architecture) in 100% Python.

Congratulations on implementing Hooks in Python; I believe you’re the first person to do that.

https://maartenbreddels.medium.com/advance-your-ipywidget-app-development-with-reacton-6734a5607d69

Once a common library for rendering/reconciliation exists, the same procedure can be repeated for Qt

It sure would be nice if we could write PySide6 apps with Reacton. Here’s an issue for talking about that.

Reacton is Awesome

This project has already saved me a bunch of time building complex Jupyter UIs. I just wanted to say thanks!

Ipyvuetify DataTable with Select in a column

Hi I am trying to create a DataTable with single row select as well as dropdown (Select) menus in one of the columns. My current issue is that changing the value of one dropdown changes the value of every dropdown (probably because they share a v_model). In the end I need to be able to pull the values of the dropdown out of the table.

import reacton.ipyvuetify as v

data = [
    {"name": "John", "age": 15, "city": "New York"},
    {"name": "Jane", "age": 23, "city": "London"},
    {"name": "Peter", "age": 32, "city": "Paris"},
    {"name": "Kate", "age": 21, "city": "Berlin"},
    {"name": "Mary", "age": 29, "city": "Mumbai"},
]

v.DataTable(
    headers=[
        {"text": "Name", "value": "name"},
        {"text": "Age", "value": "age"},
        {"text": "City", "value": "city"},
        {"text": "Selector", "value": "selector"},
    ],
    items=data,
    class_="elevation-1",
    single_select=True,
    show_select=True,
    v_slots=[
        {
            "name": "item.selector",
            "variable": "item",
            "children": v.Select(
                items=["Yes", "No"],
                v_model="item.selector",
            ),
        }
    ],
)

SelectionRangeSlider Options List Not Updating

In some cases when changing the options of a SelectionRangeSlider, the options list is not updated. For this to occur, you must have both continuous_update set to True, and an index specified. I've seen similar behavior in react_ipywidgets 0.11 (ipywidgets 0.7) with the SelectMultiple widget. I believe it's some kind of race condition in the SelectionRangeSlider's JavaScript view, but I haven't been able to reproduce this issue with ipywidgets directly.

Test Case:
Click Update, and then try and adjust the slider range. The range remains 1-3, instead of updating to 1-10.
Set continuous_update to False, or remove the index, and the problem does not occur.

import reacton as react
import reacton.ipywidgets as w

@react.component()
def Test():
    options, setOptions = react.use_state(['1', '2', '3'])
    
    firstIndex = 0
    lastIndex = max(0, len(options)-1)
    
    def onClicked():
        setOptions([str(i) for i in range(1, 11)])
        print("what")
    
    print(options)
    
    with w.VBox() as vbox:
        w.Button(description="Update", on_click=onClicked)
        w.SelectionRangeSlider(
            description="Options",
            options=options,
            index=(firstIndex, lastIndex),
            continuous_update=True
        )
    
    return vbox

Test()

Software Versions:

python                          3.10.9
ipywidgets                    8.0.4
reacton                          1.2.2

Markdown example does not work with myst_parser>=0.18

The example from https://github.com/widgetti/reacton#markdown-component-example does not work with myst_parser>=0.18 since the to_html function has been removed from the python api (see https://github.com/executablebooks/MyST-Parser/blob/28725fceb8e1e117cb247b06a267f82c501ce527/CHANGELOG.md#breaking-changes)

One solution:

@reacton.component
def Markdown(md: str):
    from markdown_it.renderer import RendererHTML
    from myst_parser.config.main import MdParserConfig
    from myst_parser.parsers.mdit import create_md_parser
    md_parser = create_md_parser(MdParserConfig(), RendererHTML)
    html = md_parser.render(md)
    return w.HTML(value=html)

BR, Alex

Amazing work - quick question

First congrats on this work, really amazing to see this. Have you looked at integrating ipydatagrid? What would that take?

Element keys set with Reacton `.key(...)` are not recognized by the ipyvuetify transition group

Hello!
I'm not sure if this issue should be opened in reacton or ipyvuetify repository.
When I try to use Vuetify transition groups, I get an error in browser console saying:
[Vue warn]: <transition-group> children must be keyed: <v-dialog>

However I used a key method on reacton elements inside the group:

with rv.FadeTransition(group=True):
    if figures.raw_measurements:
        MplFigure(*figures.raw_measurements).key("raw_measurements")
    if figures.field_accuracy:
        MplFigure(*figures.field_accuracy).key("field_accuracy")
    if figures.calibration_moment:
        MplFigure(*figures.calibration_moment).key("calibration_moment")

(MplFigure here is a @reacton.component with rv.Dialog as the root element.)
Should the same keys be used by Vue in browser?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.