Name | ipyreact JSON |
Version |
0.4.1
JSON |
| download |
home_page | |
Summary | React for ipywidgets that just works |
upload_time | 2024-02-19 16:20:35 |
maintainer | |
docs_url | None |
author | |
requires_python | >=3.7 |
license | Copyright (c) 2023 Maarten A. Breddels All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
keywords |
ipython
jupyter
widgets
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
|
# ipyreact
React for ipywidgets that just works. No webpack, no npm, no hassle. Just write jsx, tsx and python.
Build on top of [AnyWidget](https://anywidget.dev/).
## Why
Ipyreact adds composability, allowing you to add children to your widget, which will render the whole react tree in
a single react context, without adding extra divs or creating a new react context.
This allows wrapping libraries such as [Material UI](https://mui.com/), [Ant Design](https://ant.design/) and even
[React-three-fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction).
## Tutorial
This tutorial will walk you through the steps of building a complete ipywidget with react.
[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=full_tutorial.ipynb)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Ffull_tutorial.ipynb)
Just click the JupyterLite or Binder link to start the interactive walkthrough.
## Goals
- Take any [Material UI example](https://mui.com/material-ui/react-rating/), copy/paste the code, and it should work in Jupyter Notebook, Jupyter Lab, Voila, and more specifically, [Solara](https://github.com/widgetti/solara).
- Wrap a library such as [Ant Design](https://ant.design/) giving the options to customize any JSON<->JavaScript Object (de)serialization, such as the [DatePicker](https://ant.design/components/date-picker) which uses a dayjs object internally, which cannot be serialized over the wire to Python.
- Compose widgets together to form a single react tree, with the same react context (e.g. useContext).
## Examples
### Inline code
```python
import ipyreact
class ConfettiWidget(ipyreact.ValueWidget):
_esm = """
import confetti from "canvas-confetti";
import * as React from "react";
export default function({value, setValue}) {
return <button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
};"""
ConfettiWidget()
```
![initial-30-fps-compressed](https://user-images.githubusercontent.com/1765949/233469170-c659b670-07f5-4666-a201-80dea01ebabe.gif)
(_NOTE: in the recording we used on_value, we now use setValue_)
### Hot reloading
Create a tsx file:
```tsx
// confetti.tsx
import confetti from "canvas-confetti";
import * as React from "react";
export default function ({ value, setValue }) {
return (
<button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
);
}
```
And use it in your python code:
```python
import ipyreact
import pathlib
class ConfettiWidget(ipyreact.ValueWidget):
_esm = pathlib.Path("confetti.tsx")
ConfettiWidget()
```
Now edit, save, and see the changes in your browser/notebook.
![hot-reload-compressed](https://user-images.githubusercontent.com/1765949/233470113-b2aa9284-71b9-44f0-bd52-906a08b06e14.gif)
(_NOTE: in the recording we used on_value, we now use setValue_)
### IPython magic
First load the ipyreact extension:
```python
%load_ext ipyreact
```
Then use the `%%react` magic to directly write jsx/tsx in your notebook:
```tsx
%%react
import confetti from "canvas-confetti";
import * as React from "react";
export default function({value, setValue}) {
return <button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
};
```
Access the underlying widget with the name `_last_react_widget` (e.g. `_last_react_widget.value` contains the number of clicks):
![magic-optimized](https://user-images.githubusercontent.com/1765949/233471041-62e807d6-c16d-4fc5-af5d-13c0acb2c677.gif)
(_NOTE: in the recording we used on_value, we now use setValue_)
## Installation
You can install using `pip`:
```bash
pip install ipyreact
```
## Usage
### Summary
- The `ValueWidget` has an `value` trait, which is a `traitlets.Any` trait. Use this to pass data to your react component, or to get data back from your react component (since it inherits from ipywidgets.ValueWidget it
can be used in combination with ipywidgets' [interact](https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html)).
- The `ipyreact.Widget` does not have the `value` trait.
- All traits are added as props to your react component (e.g. `{value, setValue...}` pairs in the example above.
- For every trait `ipyreact` automatically provides a `set<Traitname>` callback, which you can use to set the trait value from your react component (e.g. `setValue` in the example above). (_Note: we used `on_value` before, this is now deprecated_)
- Props can de passed as `Widget(props={"title": "My title"})`, and contrary to a trait, will not add a `setTitle` callable to the props.
- Children can be passed using `Widget(children=['text', or_widget])` supporting text, widgets, and un-interrupted rendering of ipyreact widgets.
- Your code gets transpiled using [sucrase](https://github.com/alangpierce/sucrase) in the frontend, no bundler needed.
- Your code should be written in ES modules.
- Set `_debug=True` to get more debug information in the browser console.
- Make sure you export a default function from your module (e.g. `export default function MyComponent() { ... }`). This is the component that will be rendered.
- Pass `events={"onClick": handler}` to the constructor or add a method with the name `event_onClick(self, data=None)` to add a `onClick` callback to your props.
### HTML elements
You do not need to provide the module code to create built-in HTML elements, ipyreact supports the same API as [React's createElement](https://react.dev/reference/react/createElement)
allowing creation of buttons for instance.
```python
import ipyreact
ipyreact.Widget(_type="button", children=["click me"])
```
Note that in addition to all native browser elements, also web components are supported.
### Children
As shown in the above example, we also support children, which supports a list of strings (text), `ipyreact.Widget` widgets that will be rendered as an uninterrupted react tree, or
any other `ipywidgets`
```python
import ipyreact
import ipywidgets as widgets
ipyreact.Widget(_type="div", children=[
"normal text",
ipyreact.Widget(_type="button", children=["nested react widgets"]),
widgets.FloatSlider(description="regular ipywidgets")
])
```
[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=children.ipynb)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Fchildren.ipynb)
### Events
Events can be passed via the event argument. In this case `onClick` will be added as a prop to the button element.
```python
import ipyreact
ipyreact.Widget(_type="button", children=["click me"], events={"onClick": print})
```
Subclasses can also add an `event_onClick` method, which will also add a `onClick` event handler to the props.
[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=events.ipynb)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Fevents.ipynb)
### Importing external modules
Writing JSX code without having to compile/bundle is great, but so is using external libraries.
Ipyreact uses ES modules, which allows native importing of external libraries when written as an ES module.
In the example below, we use https://esm.sh/ which exposes many JS libraries as ES modules that
we can directly import.
```python
import ipyreact
ipyreact.ValueWidget(
_esm="""
import confetti from "https://esm.sh/canvas-confetti@1.6.0";
import * as React from "react";
export default function({value, setValue}) {
return <button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
};
"""
)
```
### Import maps
However, the above code now has a direct link to "https://esm.sh/canvas-confetti@1.6.0" which makes the code very specific to esm.sh.
To address this, we also support [import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) to
write code more independant of where the modules come from.
You can provide an import map using `ipyreact.define_import_map`, which takes a dictionary of module names to urls or other modules. By default we support `react` and `react-dom` which is prebundled.
Apart from `react`, the default we provide is:
```python
define_import_map({
"@mui/material": "https://esm.sh/@mui/material@5.11.10?external=react,react-dom",
"@mui/material/": "https://esm.sh/@mui/material@5.11.10&external=react,react-dom/",
"@mui/icons-material/": "https://esm.sh/@mui/icons-material/?external=react,react-dom",
"canvas-confetti": "https://esm.sh/canvas-confetti@1.6.0",
})
```
_Note that it is important to add `external=react,react-dom` for ReactJS based libraries, otherwise [esm.sh](https://esm.sh/#using-import-maps) would import ReactJS again_.
Which means we can now write our ConfettiButton as:
```python
import ipyreact
# note that this import_map is already part of the default
ipyreact.define_import_map({
"canvas-confetti": "https://esm.sh/canvas-confetti@1.6.0",
})
ipyreact.ValueWidget(
_esm="""
import confetti from "canvas-confetti";
import * as React from "react";
export default function({value, setValue}) {
return <button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
};
"""
)
```
And it also means we can copy paste _most_ of the examples from [mui](https://mui.com/)
```tsx
%%react -n my_widget -d
import {Button} from "@mui/material";
import confetti from "canvas-confetti";
import * as React from "react";
export default function({ value, setValue}) {
console.log("value=", value);
return (
<Button
variant="contained"
onClick={() => confetti() && setValue(value + 1)}
>
{value || 0} times confetti
</Button>
);
}
```
We use the https://github.com/guybedford/es-module-shims shim to the browser page for the import maps functionality.
This also means that although import maps can be configured per widget, they configuration of import maps is global.
### Bundled ESM modules
## Creating the ES module
While esm.sh is convenient to use, for production use, we recommend creating a standalone bundle. This will load faster and will not require a direct connection to esm.sh, which might not be available in airgapped or firewalled environments.
We will not create a minimal bundle for https://ant.design/
First create a simple file called `antd-minimal.js` that exports what we need.
```javascript
export { Button, Flex, Slider } from "antd";
```
Next, we install the libraries:
```bash
$ npm install antd
```
And use ESBuild to turn this into a self-contained module/bundle, without react, since ipyreact provides that for us.
```
$ npx esbuild ./antd-minimal.js --bundle --outfile=./antd-minimal.esm.js --format=esm --external:react --external:react-dom --target=esnext
```
Now we can define the module with a custom name (we call it antd-minimal).
```python
import ipyreact
from pathlib import Path
ipyreact.define_module("antd-minimal", Path("./antd-minimal.esm.js"))
```
We can now use the components from this module:
```python
def on_click(event_data):
w.children = ["Clicked"]
w = ipyreact.Widget(_module="antd-minimal", _type="Button", children=["Hi there"], events={"onClick": on_click})
w
```
Or, composing multiple ones:
```python
stack = ipyreact.Widget(_module="antd-minimal", _type="Flex",
props={"vertical": True, "style": {"padding": "24px"}},
children=[
ipyreact.Widget(_module="antd-minimal", _type="Button", children=["Ant Design Button"]),
ipyreact.Widget(_module="antd-minimal", _type="Slider",
props={"defaultValue": 3, "min": 0, "max": 11}),
])
stack
```
Input components might need a little bit of custom code, and subclassing `ValueWidget`. It often means binding the value to the right prop of the input component (in this case the Slider takes the same name, `value`) and coupling the event handler (in this case `onChange`) to the `setValue` function.
```python
import traitlets
class Slider(ipyreact.ValueWidget):
_esm = """
import * as React from "react";
import {Slider} from "antd-minimal"
export default ({value, setValue, ...rest}) => {
return <Slider value={value} onChange={(v) => setValue(v)} {...rest}/>
}
"""
s = Slider(value=2)
s
```
_Note that it depends on the implementation of the event handler if the value is being passed directly, or a (synthetic) event with the data will be passed as argument. An typical example event handler could be `onChange={(event) => setValue(event.target.value)}`._
Now the slider widget is stateful, and we have bi-directional communication using the `.value` trait.
For instance, we can read it:
```python
s.value
```
Or write to it, and it will be reflected directly in the UI.
```python
s.value = 10
```
Test this out in the notebook:
[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=antd/antd.ipynb)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Fantd%2Fantd.ipynb)
### Bundled ES modules for threejs
See this notebook for a 3D WebGL threejs-fiber example
[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=threejs-fiber/threejs-fiber.ipynb)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Fthreejs-fiber%2Fthreejs-fiber.ipynb)
## Development Installation
Create a dev environment:
```bash
conda create -n ipyreact-dev -c conda-forge nodejs yarn python jupyterlab
conda activate ipyreact-dev
```
Install the python. This will also build the TS package.
```bash
pip install -e ".[test, examples, dev]"
pre-commit install
```
When developing your extensions, you need to manually enable your extensions with the
notebook / lab frontend. For lab, this is done by the command:
```
jupyter labextension develop --overwrite .
yarn run build
```
For classic notebook, you need to run:
```
jupyter nbextension install --sys-prefix --symlink --overwrite --py ipyreact
jupyter nbextension enable --sys-prefix --py ipyreact
```
Note that the `--symlink` flag doesn't work on Windows, so you will here have to run
the `install` command every time that you rebuild your extension. For certain installations
you might also need another flag instead of `--sys-prefix`, but we won't cover the meaning
of those flags here.
### How to see your changes
#### Typescript:
If you use JupyterLab to develop then you can watch the source directory and run JupyterLab at the same time in different
terminals to watch for changes in the extension's source and automatically rebuild the widget.
```bash
# Watch the source directory in one terminal, automatically rebuilding when needed
yarn run watch
# Run JupyterLab in another terminal
jupyter lab
```
After a change wait for the build to finish and then refresh your browser and the changes should take effect.
#### Python:
If you make a change to the python code then you will need to restart the notebook kernel to have it take effect.
# FAQ
## Which version of React do you use.
We currently only support React 18. Although we have some scaffolding in place to support different version, we do not have funding to support both.
## Why does ipyreact provides React?
If several ReactJS components need to be composed into a single React app, they need to share the same React context. This makes it possible
for features such as React's [useContext](https://react.dev/reference/react/useContext) to work across the whole React tree.
If every library brings its own React, they cannot communicate using this. Also, every child would need to be nested in its own `<div>`
which can affect the layout of your application. When ipyreact provides React, we can build a true ReactJS application with a normal/true
React render tree.
## I get a React error
For instance, if you see `"Cannot read properties of null (reading 'useReducer')"` it means that you are loading in your own ReactJS version.
If you use https://esh.sh, make sure you add `??external=react,react-dom` at the end of the url, so that your esm bundle doesn't include its own
ReactJS version, but uses the one provided with ipyreact.
If you make your own bundle using esbuild, make sure to add the `--external:react --external:react-dom` flags on the CLI.
Raw data
{
"_id": null,
"home_page": "",
"name": "ipyreact",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": "",
"keywords": "IPython,Jupyter,Widgets",
"author": "",
"author_email": "\"Maarten A. Breddels\" <maartenbreddels@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/04/05/9c54b55a06574f11238fbf985e442d8bbcc28c4adbc0143d19c6daa6a2f9/ipyreact-0.4.1.tar.gz",
"platform": null,
"description": "# ipyreact\n\nReact for ipywidgets that just works. No webpack, no npm, no hassle. Just write jsx, tsx and python.\n\nBuild on top of [AnyWidget](https://anywidget.dev/).\n\n## Why\n\nIpyreact adds composability, allowing you to add children to your widget, which will render the whole react tree in\na single react context, without adding extra divs or creating a new react context.\n\nThis allows wrapping libraries such as [Material UI](https://mui.com/), [Ant Design](https://ant.design/) and even\n[React-three-fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction).\n\n## Tutorial\n\nThis tutorial will walk you through the steps of building a complete ipywidget with react.\n\n[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=full_tutorial.ipynb)\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Ffull_tutorial.ipynb)\n\nJust click the JupyterLite or Binder link to start the interactive walkthrough.\n\n## Goals\n\n- Take any [Material UI example](https://mui.com/material-ui/react-rating/), copy/paste the code, and it should work in Jupyter Notebook, Jupyter Lab, Voila, and more specifically, [Solara](https://github.com/widgetti/solara).\n- Wrap a library such as [Ant Design](https://ant.design/) giving the options to customize any JSON<->JavaScript Object (de)serialization, such as the [DatePicker](https://ant.design/components/date-picker) which uses a dayjs object internally, which cannot be serialized over the wire to Python.\n- Compose widgets together to form a single react tree, with the same react context (e.g. useContext).\n\n## Examples\n\n### Inline code\n\n```python\nimport ipyreact\n\n\nclass ConfettiWidget(ipyreact.ValueWidget):\n _esm = \"\"\"\n import confetti from \"canvas-confetti\";\n import * as React from \"react\";\n\n export default function({value, setValue}) {\n return <button onClick={() => confetti() && setValue(value + 1)}>\n {value || 0} times confetti\n </button>\n };\"\"\"\nConfettiWidget()\n```\n\n![initial-30-fps-compressed](https://user-images.githubusercontent.com/1765949/233469170-c659b670-07f5-4666-a201-80dea01ebabe.gif)\n\n(_NOTE: in the recording we used on_value, we now use setValue_)\n\n### Hot reloading\n\nCreate a tsx file:\n\n```tsx\n// confetti.tsx\nimport confetti from \"canvas-confetti\";\nimport * as React from \"react\";\n\nexport default function ({ value, setValue }) {\n return (\n <button onClick={() => confetti() && setValue(value + 1)}>\n {value || 0} times confetti\n </button>\n );\n}\n```\n\nAnd use it in your python code:\n\n```python\nimport ipyreact\nimport pathlib\n\n\nclass ConfettiWidget(ipyreact.ValueWidget):\n _esm = pathlib.Path(\"confetti.tsx\")\n\nConfettiWidget()\n```\n\nNow edit, save, and see the changes in your browser/notebook.\n\n![hot-reload-compressed](https://user-images.githubusercontent.com/1765949/233470113-b2aa9284-71b9-44f0-bd52-906a08b06e14.gif)\n\n(_NOTE: in the recording we used on_value, we now use setValue_)\n\n### IPython magic\n\nFirst load the ipyreact extension:\n\n```python\n%load_ext ipyreact\n```\n\nThen use the `%%react` magic to directly write jsx/tsx in your notebook:\n\n```tsx\n%%react\nimport confetti from \"canvas-confetti\";\nimport * as React from \"react\";\n\nexport default function({value, setValue}) {\n return <button onClick={() => confetti() && setValue(value + 1)}>\n {value || 0} times confetti\n </button>\n};\n```\n\nAccess the underlying widget with the name `_last_react_widget` (e.g. `_last_react_widget.value` contains the number of clicks):\n\n![magic-optimized](https://user-images.githubusercontent.com/1765949/233471041-62e807d6-c16d-4fc5-af5d-13c0acb2c677.gif)\n\n(_NOTE: in the recording we used on_value, we now use setValue_)\n\n## Installation\n\nYou can install using `pip`:\n\n```bash\npip install ipyreact\n```\n\n## Usage\n\n### Summary\n\n- The `ValueWidget` has an `value` trait, which is a `traitlets.Any` trait. Use this to pass data to your react component, or to get data back from your react component (since it inherits from ipywidgets.ValueWidget it\n can be used in combination with ipywidgets' [interact](https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html)).\n- The `ipyreact.Widget` does not have the `value` trait.\n- All traits are added as props to your react component (e.g. `{value, setValue...}` pairs in the example above.\n- For every trait `ipyreact` automatically provides a `set<Traitname>` callback, which you can use to set the trait value from your react component (e.g. `setValue` in the example above). (_Note: we used `on_value` before, this is now deprecated_)\n- Props can de passed as `Widget(props={\"title\": \"My title\"})`, and contrary to a trait, will not add a `setTitle` callable to the props.\n- Children can be passed using `Widget(children=['text', or_widget])` supporting text, widgets, and un-interrupted rendering of ipyreact widgets.\n- Your code gets transpiled using [sucrase](https://github.com/alangpierce/sucrase) in the frontend, no bundler needed.\n- Your code should be written in ES modules.\n- Set `_debug=True` to get more debug information in the browser console.\n- Make sure you export a default function from your module (e.g. `export default function MyComponent() { ... }`). This is the component that will be rendered.\n- Pass `events={\"onClick\": handler}` to the constructor or add a method with the name `event_onClick(self, data=None)` to add a `onClick` callback to your props.\n\n### HTML elements\n\nYou do not need to provide the module code to create built-in HTML elements, ipyreact supports the same API as [React's createElement](https://react.dev/reference/react/createElement)\nallowing creation of buttons for instance.\n\n```python\nimport ipyreact\nipyreact.Widget(_type=\"button\", children=[\"click me\"])\n```\n\nNote that in addition to all native browser elements, also web components are supported.\n\n### Children\n\nAs shown in the above example, we also support children, which supports a list of strings (text), `ipyreact.Widget` widgets that will be rendered as an uninterrupted react tree, or\nany other `ipywidgets`\n\n```python\nimport ipyreact\nimport ipywidgets as widgets\nipyreact.Widget(_type=\"div\", children=[\n \"normal text\",\n ipyreact.Widget(_type=\"button\", children=[\"nested react widgets\"]),\n widgets.FloatSlider(description=\"regular ipywidgets\")\n])\n```\n\n[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=children.ipynb)\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Fchildren.ipynb)\n\n### Events\n\nEvents can be passed via the event argument. In this case `onClick` will be added as a prop to the button element.\n\n```python\nimport ipyreact\nipyreact.Widget(_type=\"button\", children=[\"click me\"], events={\"onClick\": print})\n```\n\nSubclasses can also add an `event_onClick` method, which will also add a `onClick` event handler to the props.\n\n[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=events.ipynb)\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Fevents.ipynb)\n\n### Importing external modules\n\nWriting JSX code without having to compile/bundle is great, but so is using external libraries.\n\nIpyreact uses ES modules, which allows native importing of external libraries when written as an ES module.\nIn the example below, we use https://esm.sh/ which exposes many JS libraries as ES modules that\nwe can directly import.\n\n```python\nimport ipyreact\n\nipyreact.ValueWidget(\n _esm=\"\"\"\n import confetti from \"https://esm.sh/canvas-confetti@1.6.0\";\n import * as React from \"react\";\n\n export default function({value, setValue}) {\n return <button onClick={() => confetti() && setValue(value + 1)}>\n {value || 0} times confetti\n </button>\n };\n \"\"\"\n)\n```\n\n### Import maps\n\nHowever, the above code now has a direct link to \"https://esm.sh/canvas-confetti@1.6.0\" which makes the code very specific to esm.sh.\n\nTo address this, we also support [import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) to\nwrite code more independant of where the modules come from.\nYou can provide an import map using `ipyreact.define_import_map`, which takes a dictionary of module names to urls or other modules. By default we support `react` and `react-dom` which is prebundled.\n\nApart from `react`, the default we provide is:\n\n```python\ndefine_import_map({\n \"@mui/material\": \"https://esm.sh/@mui/material@5.11.10?external=react,react-dom\",\n \"@mui/material/\": \"https://esm.sh/@mui/material@5.11.10&external=react,react-dom/\",\n \"@mui/icons-material/\": \"https://esm.sh/@mui/icons-material/?external=react,react-dom\",\n \"canvas-confetti\": \"https://esm.sh/canvas-confetti@1.6.0\",\n})\n```\n\n_Note that it is important to add `external=react,react-dom` for ReactJS based libraries, otherwise [esm.sh](https://esm.sh/#using-import-maps) would import ReactJS again_.\n\nWhich means we can now write our ConfettiButton as:\n\n```python\nimport ipyreact\n\n# note that this import_map is already part of the default\nipyreact.define_import_map({\n \"canvas-confetti\": \"https://esm.sh/canvas-confetti@1.6.0\",\n})\n\n\nipyreact.ValueWidget(\n _esm=\"\"\"\n import confetti from \"canvas-confetti\";\n import * as React from \"react\";\n\n export default function({value, setValue}) {\n return <button onClick={() => confetti() && setValue(value + 1)}>\n {value || 0} times confetti\n </button>\n };\n \"\"\"\n)\n```\n\nAnd it also means we can copy paste _most_ of the examples from [mui](https://mui.com/)\n\n```tsx\n%%react -n my_widget -d\nimport {Button} from \"@mui/material\";\nimport confetti from \"canvas-confetti\";\nimport * as React from \"react\";\n\nexport default function({ value, setValue}) {\n console.log(\"value=\", value);\n return (\n <Button\n variant=\"contained\"\n onClick={() => confetti() && setValue(value + 1)}\n >\n {value || 0} times confetti\n </Button>\n );\n}\n```\n\nWe use the https://github.com/guybedford/es-module-shims shim to the browser page for the import maps functionality.\nThis also means that although import maps can be configured per widget, they configuration of import maps is global.\n\n### Bundled ESM modules\n\n## Creating the ES module\n\nWhile esm.sh is convenient to use, for production use, we recommend creating a standalone bundle. This will load faster and will not require a direct connection to esm.sh, which might not be available in airgapped or firewalled environments.\n\nWe will not create a minimal bundle for https://ant.design/\n\nFirst create a simple file called `antd-minimal.js` that exports what we need.\n\n```javascript\nexport { Button, Flex, Slider } from \"antd\";\n```\n\nNext, we install the libraries:\n\n```bash\n$ npm install antd\n```\n\nAnd use ESBuild to turn this into a self-contained module/bundle, without react, since ipyreact provides that for us.\n\n```\n$ npx esbuild ./antd-minimal.js --bundle --outfile=./antd-minimal.esm.js --format=esm --external:react --external:react-dom --target=esnext\n```\n\nNow we can define the module with a custom name (we call it antd-minimal).\n\n```python\nimport ipyreact\nfrom pathlib import Path\n\nipyreact.define_module(\"antd-minimal\", Path(\"./antd-minimal.esm.js\"))\n```\n\nWe can now use the components from this module:\n\n```python\ndef on_click(event_data):\n w.children = [\"Clicked\"]\n\nw = ipyreact.Widget(_module=\"antd-minimal\", _type=\"Button\", children=[\"Hi there\"], events={\"onClick\": on_click})\nw\n```\n\nOr, composing multiple ones:\n\n```python\nstack = ipyreact.Widget(_module=\"antd-minimal\", _type=\"Flex\",\n props={\"vertical\": True, \"style\": {\"padding\": \"24px\"}},\n children=[\n ipyreact.Widget(_module=\"antd-minimal\", _type=\"Button\", children=[\"Ant Design Button\"]),\n ipyreact.Widget(_module=\"antd-minimal\", _type=\"Slider\",\n props={\"defaultValue\": 3, \"min\": 0, \"max\": 11}),\n])\nstack\n```\n\nInput components might need a little bit of custom code, and subclassing `ValueWidget`. It often means binding the value to the right prop of the input component (in this case the Slider takes the same name, `value`) and coupling the event handler (in this case `onChange`) to the `setValue` function.\n\n```python\nimport traitlets\n\n\nclass Slider(ipyreact.ValueWidget):\n _esm = \"\"\"\n import * as React from \"react\";\n import {Slider} from \"antd-minimal\"\n\n export default ({value, setValue, ...rest}) => {\n return <Slider value={value} onChange={(v) => setValue(v)} {...rest}/>\n }\n\n \"\"\"\ns = Slider(value=2)\ns\n```\n\n_Note that it depends on the implementation of the event handler if the value is being passed directly, or a (synthetic) event with the data will be passed as argument. An typical example event handler could be `onChange={(event) => setValue(event.target.value)}`._\n\nNow the slider widget is stateful, and we have bi-directional communication using the `.value` trait.\nFor instance, we can read it:\n\n```python\ns.value\n```\n\nOr write to it, and it will be reflected directly in the UI.\n\n```python\ns.value = 10\n```\n\nTest this out in the notebook:\n[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=antd/antd.ipynb)\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Fantd%2Fantd.ipynb)\n\n### Bundled ES modules for threejs\n\nSee this notebook for a 3D WebGL threejs-fiber example\n\n[![JupyterLight](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://widgetti.github.io/ipyreact/lab/?path=threejs-fiber/threejs-fiber.ipynb)\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/widgetti/ipyreact/HEAD?labpath=examples%2Fthreejs-fiber%2Fthreejs-fiber.ipynb)\n\n## Development Installation\n\nCreate a dev environment:\n\n```bash\nconda create -n ipyreact-dev -c conda-forge nodejs yarn python jupyterlab\nconda activate ipyreact-dev\n```\n\nInstall the python. This will also build the TS package.\n\n```bash\npip install -e \".[test, examples, dev]\"\npre-commit install\n```\n\nWhen developing your extensions, you need to manually enable your extensions with the\nnotebook / lab frontend. For lab, this is done by the command:\n\n```\njupyter labextension develop --overwrite .\nyarn run build\n```\n\nFor classic notebook, you need to run:\n\n```\njupyter nbextension install --sys-prefix --symlink --overwrite --py ipyreact\njupyter nbextension enable --sys-prefix --py ipyreact\n```\n\nNote that the `--symlink` flag doesn't work on Windows, so you will here have to run\nthe `install` command every time that you rebuild your extension. For certain installations\nyou might also need another flag instead of `--sys-prefix`, but we won't cover the meaning\nof those flags here.\n\n### How to see your changes\n\n#### Typescript:\n\nIf you use JupyterLab to develop then you can watch the source directory and run JupyterLab at the same time in different\nterminals to watch for changes in the extension's source and automatically rebuild the widget.\n\n```bash\n# Watch the source directory in one terminal, automatically rebuilding when needed\nyarn run watch\n# Run JupyterLab in another terminal\njupyter lab\n```\n\nAfter a change wait for the build to finish and then refresh your browser and the changes should take effect.\n\n#### Python:\n\nIf you make a change to the python code then you will need to restart the notebook kernel to have it take effect.\n\n# FAQ\n\n## Which version of React do you use.\n\nWe currently only support React 18. Although we have some scaffolding in place to support different version, we do not have funding to support both.\n\n## Why does ipyreact provides React?\n\nIf several ReactJS components need to be composed into a single React app, they need to share the same React context. This makes it possible\nfor features such as React's [useContext](https://react.dev/reference/react/useContext) to work across the whole React tree.\nIf every library brings its own React, they cannot communicate using this. Also, every child would need to be nested in its own `<div>`\nwhich can affect the layout of your application. When ipyreact provides React, we can build a true ReactJS application with a normal/true\nReact render tree.\n\n## I get a React error\n\nFor instance, if you see `\"Cannot read properties of null (reading 'useReducer')\"` it means that you are loading in your own ReactJS version.\n\nIf you use https://esh.sh, make sure you add `??external=react,react-dom` at the end of the url, so that your esm bundle doesn't include its own\nReactJS version, but uses the one provided with ipyreact.\n\nIf you make your own bundle using esbuild, make sure to add the `--external:react --external:react-dom` flags on the CLI.\n",
"bugtrack_url": null,
"license": "Copyright (c) 2023 Maarten A. Breddels All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.",
"summary": "React for ipywidgets that just works",
"version": "0.4.1",
"project_urls": {
"Homepage": "https://github.com/widgetti/ipyreact"
},
"split_keywords": [
"ipython",
"jupyter",
"widgets"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "d0b9f51936102e7a6469e2883e8f8ff61d33ec8b642bd57e05073305e30d3f83",
"md5": "12ad18000a02f7b39cbbc0fd425043a9",
"sha256": "cca005a35792ee930d23191b446a7efdaba13f11d27dc7ee34a70eb77c9e2821"
},
"downloads": -1,
"filename": "ipyreact-0.4.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "12ad18000a02f7b39cbbc0fd425043a9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 1266114,
"upload_time": "2024-02-19T16:20:33",
"upload_time_iso_8601": "2024-02-19T16:20:33.059960Z",
"url": "https://files.pythonhosted.org/packages/d0/b9/f51936102e7a6469e2883e8f8ff61d33ec8b642bd57e05073305e30d3f83/ipyreact-0.4.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "04059c54b55a06574f11238fbf985e442d8bbcc28c4adbc0143d19c6daa6a2f9",
"md5": "1db84ac428e0df1fc55d5ca46ab61fd1",
"sha256": "80560311eb4946a793d1108d21c893f697f45af71df52afe4a3cddfa0a9fadd4"
},
"downloads": -1,
"filename": "ipyreact-0.4.1.tar.gz",
"has_sig": false,
"md5_digest": "1db84ac428e0df1fc55d5ca46ab61fd1",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 1861076,
"upload_time": "2024-02-19T16:20:35",
"upload_time_iso_8601": "2024-02-19T16:20:35.226434Z",
"url": "https://files.pythonhosted.org/packages/04/05/9c54b55a06574f11238fbf985e442d8bbcc28c4adbc0143d19c6daa6a2f9/ipyreact-0.4.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-02-19 16:20:35",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "widgetti",
"github_project": "ipyreact",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "ipyreact"
}