{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "\"ReactPy\n", "\n", "---\n", "\n", "[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." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# At a Glance\n", "\n", "To get a rough idea of how to write apps in ReactPy, take a look at the tiny [“hello world”](https://en.wikipedia.org/wiki/%22Hello,_World!%22_program) application below:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "from reactpy import component, html\n", "\n", "\n", "@component\n", "def App():\n", " return html.h1(\"Hello, World!\")\n", "\n", "\n", "App()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Creating Interfaces\n", "\n", "ReactPy is a Python package for making user interfaces (UI). These interfaces are built from small elements of functionality like buttons text and images. ReactPy allows you to combine these elements into reusable “components”. Once you learn how these UI elements are created and organized into components you'll be able to do things like create interfaces from raw data:\n", "\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "from reactpy import component, html\n", "\n", "\n", "@component\n", "def DataList(items, filter_by_priority=None, sort_by_priority=False):\n", " if filter_by_priority is not None:\n", " items = [i for i in items if i[\"priority\"] <= filter_by_priority]\n", " if sort_by_priority:\n", " items = list(sorted(items, key=lambda i: i[\"priority\"]))\n", " list_item_elements = [html.li({\"key\": i[\"id\"]}, i[\"text\"]) for i in items]\n", " return html.ul(list_item_elements)\n", "\n", "\n", "@component\n", "def TodoList():\n", " tasks = [\n", " {\"id\": 0, \"text\": \"Make breakfast\", \"priority\": 0},\n", " {\"id\": 1, \"text\": \"Feed the dog\", \"priority\": 0},\n", " {\"id\": 2, \"text\": \"Do laundry\", \"priority\": 2},\n", " {\"id\": 3, \"text\": \"Go on a run\", \"priority\": 1},\n", " {\"id\": 4, \"text\": \"Clean the house\", \"priority\": 2},\n", " {\"id\": 5, \"text\": \"Go to the grocery store\", \"priority\": 2},\n", " {\"id\": 6, \"text\": \"Do some coding\", \"priority\": 1},\n", " {\"id\": 7, \"text\": \"Read a book\", \"priority\": 1},\n", " ]\n", " return html.section(\n", " html.h1(\"My Todo List\"),\n", " DataList(tasks, filter_by_priority=1, sort_by_priority=True),\n", " )\n", "\n", "\n", "TodoList()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Adding Interactivity\n", "\n", "Components often need to change what’s on the screen as a result of an interaction. For example, typing into the form should update the input field, and clicking a “Comment” button should bring up a text input field, clicking “Buy” should put a product in the shopping cart. Components need to “remember” things like the current input value, the current image, and the shopping cart. In ReactPy, this kind of component-specific memory is created and updated with a “hook” called use_state() that creates a state variable and state setter respectively:\n", "\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "import json\n", "from pathlib import Path\n", "\n", "from reactpy import component, use_state, html\n", "\n", "\n", "DATA_PATH = Path().parent / \"data\" / \"gallery-data.json\"\n", "sculpture_data = json.loads(DATA_PATH.read_text())\n", "\n", "\n", "@component\n", "def Gallery():\n", " index, set_index = use_state(0)\n", "\n", " def handle_click(event):\n", " set_index(index + 1)\n", "\n", " bounded_index = index % len(sculpture_data)\n", " sculpture = sculpture_data[bounded_index]\n", " alt = sculpture[\"alt\"]\n", " artist = sculpture[\"artist\"]\n", " description = sculpture[\"description\"]\n", " name = sculpture[\"name\"]\n", " url = sculpture[\"url\"]\n", "\n", " return html.div(\n", " html.button({\"onClick\": handle_click}, \"Next\"),\n", " html.h2(name, \" by \", artist),\n", " html.p(f\"({bounded_index + 1} or {len(sculpture_data)})\"),\n", " html.img({\"src\": url, \"alt\": alt, \"style\": {\"height\": \"200px\"}}),\n", " html.p(description),\n", " )\n", "\n", "\n", "Gallery()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Using ReactPy With Jupyter Widgets\n", "\n", "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, "metadata": {}, "outputs": [], "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" ] }, { "attachments": {}, "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": null, "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", " def register_observer():\n", " def handle_change(change):\n", " set_value(change[\"new\"])\n", "\n", " # observe the slider's value\n", " slider.observe(handle_change, \"value\")\n", " # unobserve the slider's value if this component is no longer displayed\n", " return lambda: slider.unobserve(handle_change, \"value\")\n", "\n", " return html.div(\n", " slider_component, html.p(f\"ReactPy observes the value to be: \", value)\n", " )" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Now you need to pass the `SliderObserver` component an `IntSlider` widget and display it.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "from ipywidgets import IntSlider\n", "\n", "SliderObserver(IntSlider(readout=False))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "You can also include ReactPy components within Jupyter Widgets using `reactpy_jupyter.to_widget`" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "from ipywidgets import Box\n", "from reactpy_jupyter import to_widget\n", "\n", "slider = IntSlider(readout=False)\n", "slider_observer_widget = to_widget(SliderObserver(slider))\n", "\n", "Box([slider, slider_observer_widget])" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "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": null, "metadata": {}, "outputs": [], "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])" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Javascript Integration\n", "\n", "While ReactPy is a great tool for displaying HTML and responding to browser events with pure Python, there are other projects which already allow you to do this inside Jupyter Notebooks or in standard web apps. The real power of ReactPy comes from its ability to seamlessly leverage the existing Javascript ecosystem:\n", "\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "from reactpy import component, web\n", "\n", "\n", "victory = web.module_from_template(\"react\", \"victory-bar\", fallback=\"⌛\")\n", "VictoryBar = web.export(victory, \"VictoryBar\")\n", "\n", "\n", "@component\n", "def Demo():\n", " bar_style = {\"parent\": {\"width\": \"500px\"}, \"data\": {\"fill\": \"royalblue\"}}\n", " return VictoryBar({\"style\": bar_style})\n", "\n", "\n", "Demo()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# [Learn More!](https://reactpy.dev/docs/index.html)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.1" }, "vscode": { "interpreter": { "hash": "52bee9646c8f5526f0e9a8eb5a97552aae62c8d54f85864da3e91d79a91aa9b6" } } }, "nbformat": 4, "nbformat_minor": 4 }