Skip to content

allow widgets to be used in reactpy components #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"env": {
"browser": true,
"node": true,
"es2021": true
},
"extends": ["eslint:recommended"],
"overrides": [],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
}
}
241 changes: 178 additions & 63 deletions notebooks/introduction.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# What is ReactPy?\n",
"<img src=\"https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-landscape.svg\" alt=\"ReactPy Logo\" style=\"min-width: 300px; width: 35%\" />\n",
"\n",
"ReactPy connects your Python web framework of choice to a ReactJS frontend, allowing you to create **interactive websites without needing JavaScript!**\n",
"---\n",
"\n",
"Following ReactJS styling, web elements are combined into [reusable \"components\"](https://reactpy.dev/docs/guides/creating-interfaces/your-first-components/index.html#parametrizing-components). These components can utilize [hooks](https://reactpy.dev/docs/reference/hooks-api.html) and [events](https://reactpy.dev/docs/guides/adding-interactivity/responding-to-events/index.html#async-event-handlers) to create infinitely complex web pages.\n",
"\n",
"When needed, ReactPy can [use components directly from NPM](https://reactpy.dev/docs/guides/escape-hatches/javascript-components.html#dynamically-loaded-components). For additional flexibility, components can also be [fully developed in JavaScript](https://reactpy.dev/docs/guides/escape-hatches/javascript-components.html#custom-javascript-components).\n",
"\n",
"\n",
"# Getting Started\n",
"\n",
"Then, before anything else, do one of the following:\n",
"\n",
"1. At the top of your notebook run\n",
"\n",
" ```python\n",
" import reactpy_jupyter\n",
" ```\n",
"\n",
"2. Register `reactpy_jupyter` as a permanant IPython extension in [your config file](https://ipython.readthedocs.io/en/stable/config/intro.html#introduction-to-ipython-configuration):\n",
"\n",
" ```python\n",
" c.InteractiveShellApp.extensions = ['reactpy_jupyter']\n",
" ```\n",
"\n",
"For the purposes of this tutorial, you'll want to do the first:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"import reactpy_jupyter"
"[ReactPy](https://reactpy.dev/) is a library for building user interfaces in Python without Javascript. ReactPy interfaces are made from components which look and behave similarly to those found in [ReactJS](https://reactjs.org/). Designed with simplicity in mind, ReactPy can be used by those without web development experience while also being powerful enough to grow with your ambitions."
]
},
{
Expand All @@ -54,13 +22,29 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"metadata": {
"tags": []
},
"outputs": [],
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "e0c3c63d3e1c4d88a71bb7bbc6ae45e0",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"LayoutWidget(Layout(ContextProvider(<function context at 0x7f7986b10cc0>)))"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from reactpy import component, html, run\n",
"from reactpy import component, html\n",
"\n",
"\n",
"@component\n",
Expand All @@ -84,11 +68,27 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"metadata": {
"tags": []
},
"outputs": [],
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a87baeb77dcc44e1a29ab154df1dd8a7",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"LayoutWidget(Layout(ContextProvider(<function context at 0x7f7986b10cc0>)))"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from reactpy import component, html\n",
"\n",
Expand Down Expand Up @@ -137,11 +137,27 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 3,
"metadata": {
"tags": []
},
"outputs": [],
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "920e5f89aba644528257e20deae25f2e",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"LayoutWidget(Layout(ContextProvider(<function context at 0x7f7986b10cc0>)))"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import json\n",
"from pathlib import Path\n",
Expand Down Expand Up @@ -186,22 +202,62 @@
"source": [
"# Using ReactPy With Jupyter Widgets\n",
"\n",
"While you can use ReactPy components independently, it may also be useful to integrate them with the rest of the Jupyter Widget ecosystem. Let's consider a ReactPy component that responds to and displays changes from an `ipywidgets.IntSlider`. The ReactPy component will need to accept an `IntSlider` instance as one of its arguments, declare state that will track the slider's value, and register a lister that will update that state via the slider's `IntSlider.observe()` method using an [\"effect\"](https://reactpy.dev/docs/reference/hooks-api.html#use-effect):"
"It's possible to use Jupyter Widgets in ReactPy components if you convert them first using `reactpy_jupyter.from_widget`."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "67cb81abf4ce4c9e991a9771607b9d5e",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"LayoutWidget(Layout(ContextProvider(<function context at 0x7f7986b10cc0>)))"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from reactpy_jupyter import from_widget\n",
"from ipywidgets import IntSlider\n",
"\n",
"slider_widget = IntSlider()\n",
"slider_component = from_widget(slider_widget)\n",
"\n",
"slider_component"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's consider a ReactPy component that responds to and displays changes from an `ipywidgets.IntSlider`. The ReactPy component will need to accept an `IntSlider` instance as one of its arguments, convert it to a component with `from_widget`, declare state that will track the slider's value, and register a lister that will update that state via the slider's `IntSlider.observe()` method using an [\"effect\"](https://reactpy.dev/docs/reference/hooks-api.html#use-effect):"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from reactpy import use_effect\n",
"from reactpy_jupyter import from_widget\n",
"\n",
"\n",
"@component\n",
"def SliderObserver(slider):\n",
" slider_component = from_widget(slider)\n",
" value, set_value = use_state(0)\n",
"\n",
" @use_effect\n",
Expand All @@ -214,63 +270,122 @@
" # unobserve the slider's value if this component is no longer displayed\n",
" return lambda: slider.unobserve(handle_change, \"value\")\n",
"\n",
" return html.p(f\"ReactPy observes the value to be: \", value)"
" return html.div(\n",
" slider_component, html.p(f\"ReactPy observes the value to be: \", value)\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now you'll need to display the `SliderObserver` component as well as an `IntSlider` widget. To do this, you'll want wrap the component in a `reactpy_jupyter.LayoutWidget` instance before using it alongside other Jupyter Widgets. Specifically, you'll be displaying the `SliderObserver` and `IntSlider` in a `Box`:\n"
"Now you need to pass the `SliderObserver` component an `IntSlider` widget and display it.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 6,
"metadata": {
"tags": []
},
"outputs": [],
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "c586a0f0fe0b44a3bab173ccb9fa52d8",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"LayoutWidget(Layout(ContextProvider(<function context at 0x7f7986b10cc0>)))"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from ipywidgets import Box, IntSlider\n",
"from reactpy_jupyter import LayoutWidget\n",
"\n",
"slider = IntSlider(readout=False)\n",
"slider_observer = LayoutWidget(SliderObserver(slider))\n",
"from ipywidgets import IntSlider\n",
"\n",
"Box([slider, slider_observer])"
"SliderObserver(IntSlider(readout=False))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If it becomes painful to wrap every ReactPy component in a `LayoutWidget` you can create an alternate `LayoutWidget` constructor using `reactpy_jupyter.widgetize`:"
"You can also include ReactPy components within Jupyter Widgets using `reactpy_jupyter.to_widget`"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 8,
"metadata": {
"tags": []
},
"outputs": [],
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a0180ea4781b4dda9f77d1be75fbcad3",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Box(children=(IntSlider(value=0, readout=False), LayoutWidget(Layout(ContextProvider(<function context at 0x7f…"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from reactpy_jupyter import widgetize\n",
"\n",
"SliderObserverWidget = widgetize(SliderObserver)\n",
"from ipywidgets import Box\n",
"from reactpy_jupyter import to_widget\n",
"\n",
"slider = IntSlider(readout=False)\n",
"slider_observer = SliderObserverWidget(slider)\n",
"slider_observer_widget = to_widget(SliderObserver(slider))\n",
"\n",
"Box([slider, slider_observer])"
"Box([slider, slider_observer_widget])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It's worth noting that, while ReactPy can be used with Jupyter Widgets, the reverse is not true. That is, **you cannot use a Jupyter Widget inside a ReactPy component**. If this is a capability you would find useful, please [start a discussion](https://github.com/reactive-python/reactpy/discussions). The ReactPy team would be very interested to hear how allowing Jupyter Widgets to be used with ReactPy could facilitate your work."
"If it becomes painful to convert every ReactPy component to a jupyter widget you can create an alternate widget constructor:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "abeb4c3af2164e00927b317e5dfb6569",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Box(children=(LayoutWidget(Layout(ContextProvider(<function context at 0x7f7986b10cc0>))), LayoutWidget(Layout…"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"slider = IntSlider(readout=False)\n",
"slider_observer_constructor = to_widget(SliderObserver)\n",
"observer_1 = slider_observer_constructor(slider)\n",
"observer_2 = slider_observer_constructor(slider)\n",
"\n",
"Box([observer_1, observer_2])"
]
},
{
Expand Down
14 changes: 14 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
group = NoxOpt(auto_tag=True)


@group.session
def fix_lint(session: Session) -> None:
session.install(
"black[jupyter]",
"flake8-pyproject",
"flake8",
"isort",
)
session.run("black", ".")
session.run("isort", ".")

session.run("npm", "run", "fix:lint", external=True)


@group.session
def check_python(session: Session) -> None:
session.install(
Expand Down
Loading