Closed paddymul closed 4 months ago
Ahh, you still need to add it as a python trait to the class, the value
trait is builtin.
This works for bi-directional binding
class OtherCountWidget(ipyreact.ReactWidget):
other_count = Any(None, allow_none=True).tag(sync=True)
_esm = """
import confetti from "canvas-confetti";
import * as React from "react";
export default function({value, on_value, debug, other_count}) {
return <div><button onClick={() => confetti() && on_value(value + 1)}>
{value || 0} times confetti
</button>
<span>{other_count} other count</span>
</div>
};"""
ocw = OtherCountWidget()
ocw
ocw.other_count = 4
This is obvious to me now, but wasn't when initially reading the documentation, I'll try to code up a simple example as a PR. The example will answer some of my other questions.
The example will answer some of my other questions.
Great :)
Yeah, value is added because it's used so much https://github.com/widgetti/ipyreact/blob/0a97edb5e4a1d7c93de8ba17eb5ec82b05a8a483/ipyreact/widget.py#L31
I think we should subclass from ValueWidget in ipywidgets, that makes it possible to use it with interact
!
I added a working example here https://github.com/widgetti/ipyreact/pull/7 . How should this be referenced in the docs?
Yeah, maybe in the https://github.com/widgetti/ipyreact#examples section we can refer to that directory?
I am working on a ipyreact walkthrough notebook at https://github.com/widgetti/ipyreact/pull/11 and I want to add a section that explains how to increment a variable.
From this conversation and the README.md file I found that this example works:
import ipyreact
# π«π«π« This counter works π«π«π«
class MyFirstWidget(ipyreact.ReactWidget):
_esm = """
import * as React from "react";
export default function MyButton({value, on_value}) {
return <button onClick={() => on_value(value + 1)}>
{value || 0} times clicked
</button>
};"""
MyFirstWidget()
but then, when I add another traitlet other_count
and replace all value
variables with other_count
variables, clicking the button does not increment the other_count value.
Can some of you explain to me why this is the case?
Is it because the on_value
function is something related only to the value
parameter?
I'd be very interested in how this below example could be tweaked in a way so that it also allows incrementing the value of other_count
:
import ipyreact
from traitlets import Any
class MyFirstWidget(ipyreact.ReactWidget):
other_count = Any(None, allow_none=True).tag(sync=True)
_esm = """
import * as React from "react";
export default function MyButton({other_count, on_value}) {
return <button onClick={() => on_value(other_count + 1)}>
{other_count || 0} times clicked
</button>
};"""
MyFirstWidget()
Yes, see https://github.com/widgetti/ipyreact#facts, for every trait <name>
there is a <name>
, on_<name>
pair on the react/frontend side.
https://github.com/widgetti/ipyreact/blob/a9e40b1100a6066265e310c83bfc667be4417b5d/ipyreact/widget.py#L31 value
is a trait we already provide by default.
which means
return <button onClick={() => on_value(other_count + 1)}>
should be
return <button onClick={() => on_other_count(other_count + 1)}>
Ohh nice! This works! πΊπΌ
import ipyreact
from traitlets import Int
class MyFirstWidget(ipyreact.ReactWidget):
my_count = Int(None, allow_none=True).tag(sync=True)
_esm = """
import * as React from "react";
export default function MyButton({my_count, on_my_count}) {
return <button onClick={() => on_my_count(my_count + 1)}>
{my_count || 0} times clicked
</button>
};"""
MyFirstWidget()
one more question. When I have this example here:
import ipyreact
from traitlets import Int
class MyFirstWidget(ipyreact.ReactWidget):
my_count = Int(0).tag(sync=True)
_esm = """
import * as React from "react";
export default function MyButton({my_count, on_my_count}) {
return <button onClick={() => on_my_count(my_count + 1)}>
{my_count} times clicked
</button>
};"""
m = MyFirstWidget()
m
Is there a way I can relocate the on_my_count
logic to the python side?
This is how I would imagine something like that to look like:
import ipyreact
from traitlets import Int,observe
class MyCounterWidget(ipyreact.ReactWidget):
my_count = Int(0).tag(sync=True)
def my_python_function(self,count):
self.my_count = count + 1
_esm = """
import * as React from "react";
export default function MyButton({my_count}) {
return <button onClick={() => my_python_function(my_count) }>
{my_count} times clicked
</button>
};"""
m = MyCounterWidget()
m
See https://github.com/widgetti/ipyreact/pull/8 what do you think of this pr?
amazing! That works out of the box! One question regarding best practice: I tested #8 and both examples below are working, one using
def on_my_python_function(self):
self.my_count = self.my_count + 1
and the other using
def on_python_function(self,count):
self.my_count = count + 1
Does one approach have an advantage about the other one? here is the full code:
import ipyreact
from traitlets import Int, Unicode
class Widget1(ipyreact.ReactWidget):
my_count = Int(0).tag(sync=True)
label = Unicode("Click me").tag(sync=True)
def on_my_python_function(self):
self.my_count = self.my_count + 1
self.label = f"Clicked {self.my_count}"
_esm = """
import * as React from "react";
export default function({on_my_python_function, label}) {
return(
<div>
<button onClick={() => on_my_python_function()}>
{label}
</button>
</div>
)
};
"""
w1 = Widget1()
w1
import ipyreact
from traitlets import Int, Unicode
class Widget2(ipyreact.ReactWidget):
my_count = Int(0).tag(sync=True)
label = Unicode("Click me").tag(sync=True)
def on_python_function(self,count):
self.my_count = count + 1
self.label = f"Clicked {self.my_count}"
_esm = """
import * as React from "react";
export default function MyButton({on_python_function , label, my_count}) {
return <button onClick={(event) => on_python_function(my_count) }>
{label}
</button>
};"""
w2 = Widget2()
w2
They are slightly different. The first, will use the my_count
Python property/trait, which uses the property itself. Imagine there are two events in flight, and they get executed after each other: `self.my_count=2
.
In the second case, there are two message in flight, but with with the same payload (my_count=0
). After executing the method twice, my_count=1
.
We now have ValueWidget
which has a special value
trait, and a Widget
which only has props, children and events: https://github.com/widgetti/ipyreact?tab=readme-ov-file#usage
I tried to add an "other_value" prop to the example, expecting to get a trait for "other_value"
ocw.traits()['other_count']
throws an error.Is the idea that all component state should be passed from python in
value
?Is the recommended practice to break out from value to individual props for a subcomponent... so that the default passed component just becomes a wrapper?