Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Minimal example that incorporates react components #97

Closed
kolibril13 opened this issue Mar 25, 2023 · 5 comments
Closed

Minimal example that incorporates react components #97

kolibril13 opened this issue Mar 25, 2023 · 5 comments

Comments

@kolibril13
Copy link
Collaborator

I think there's a lot of potential in bringing react components into the ipywidget ecosystem.
This would open the door to projects like an https://www.tldraw.com/ based plot annotation tool, or interactive https://mafs.dev/ components for teaching math.

There's a minimal react example at
https://react.dev/learn/tutorial-tic-tac-toe#setup-for-the-tutorial

export default function Square() {
  return <button className="square">X</button>;
}

I don't know much about the react bundling process, therefore the question to you @manzt : would you have time and interest to investigate if it's possible to incorporate this minimal react component into an anywidget widget? That would be amazing!
And I think that other people from the community would be interested in this as well, e.g. @maartenbreddels https://discourse.jupyter.org/t/need-to-guidance-to-integrate-react-js-application-into-jupyter-extension/18477/3

@manzt
Copy link
Owner

manzt commented Mar 26, 2023

TL;DR - Totally possible today to create and use React widgets with anywidget! I just haven't added to the documentation because 1.) it requires a bit of boilerplate and 2.) I'm working on a way to support react/other frameworks with less boilerplate.

Thanks for the suggestion! I agree that it would be great to make it easier to bring React components into Jupyter widgets ecosystem. That said, I want anywidget at its core to remain agnostic to the many frontend frameworks but provide a standard for declaring widgets using web standards.

For example, a React widget could be defined today with anywidget via:

// index.jsx
import * as React from "https://esm.sh/react@18";
import * as ReactDOM from "https://esm.sh/react-dom@18/client";

function Square() {
  return <button className="square">X</button>;
}

export function render(view) {
  let root = ReactDOM.createRoot(view.el);
  root.render(<Square />);
  return () => root.unmount();
}

but it requires using a bundler (like esbuild) to convert the JSX above into into "pure" ESM (index.jsx -> bundle.js).

npx esbuild --format=esm --outfile=bundle.js index.jsx
output `bundle.js`
import * as React from "https://esm.sh/react@18";
import * as ReactDOM from "https://esm.sh/react-dom@18/client";
function Square() {
  return /* @__PURE__ */ React.createElement("button", { className: "square" }, "X");
}
function render(view) {
  let root = ReactDOM.createRoott(view.el);
  root.render(/* @__PURE__ */ React.createElement(Square, null));
  return () => root.unmount();
}
export {
  render
};
import pathlib
import anywidget

class Square(anywidget.AnyWidget):
    _esm = pathlib.Path("bundle.js") # the output of esbuild

My vision for supporting frameworks like React is to reduce this kind of boilerplate, but in a way that sits on top of this ESM "core". I am currently toying with some ideas for integrating a bundler and supporting default export components from React like the snippet you suggested (probably in a submodule like anywidget.react). I think another goal should be to provide some utilties for the front-end code to connect react state with the backbone widget model (e.g., custom hooks).

import { useModelState } from "anywidget:react/hooks";

export default function Counter() {
  let [count, setCount] = useModelState("count");
  return <button onClick={() => setCount(count++)} >count is {count}</button>
}

@kolibril13
Copy link
Collaborator Author

this is so cool, your code works out of the box and correctly renders the square in the notebook, thanks a lot for this example! 🎉

Just noted two small typos

  • in bundle.js: createRoott -> createRoot
  • in the notebook: anywidget.anywidget -> anywidget.AnyWidget
    Here is the updated script:
//bundle.js
import * as React from "https://esm.sh/react@18";
import * as ReactDOM from "https://esm.sh/react-dom@18/client";
function Square() {
  return /* @__PURE__ */ React.createElement("button", { className: "square" }, "X");
}
function render(view) {
  let root = ReactDOM.createRoot(view.el);
  root.render(/* @__PURE__ */ React.createElement(Square, null));
  return () => root.unmount();
}
export {
  render
};
# example.ipynb
import pathlib
import anywidget

class Square(anywidget.AnyWidget):
    _esm = pathlib.Path("bundle.js")
    
Square()

image

I am really looking forward to where this project will lead to, and can imagine a future where many workflows that currently require lots of application switching can instead happen in only one notebook.
Iterating on ideas will become so much more fun!
E.g. no more need to copying a plot from the notebook output into a separate post-production drawing app like tldraw.com and then copy that file back into the project, but having the drawing app directly in the notebook with automatic import and export of assets from the python kernel ✨

@manzt
Copy link
Owner

manzt commented Mar 26, 2023

Thanks for your enthusiasm and attention to detail in the code snippets I shared (and apologies for not testing locally)! For now, I'm going to pin this issue so that others who want to try out react can hopefully find it quickly. I'm hoping eventually we will have a nicer solution that should live in the docs.

I am really looking forward to where this project will lead to, and can imagine a future where many workflows that currently require lots of application switching can instead happen in only one notebook.
Iterating on ideas will become so much more fun!

Me too! Love to hear it and see what you end up coming up with.

@maartenbreddels
Copy link

Thanks for the ping @kolibril13 !

Yes, I'm quite interested in this area. Actually, I've been talking to @manzt about support for React in anywidgets a few weeks ago. But I'm unsure what our conclusions were.

Based on our discussion then, I did create a prototype that uses babel in the browser and using Anywidget for the HRM support, I've cobbled together:

import ipyreact
import pathlib

class Demo(ipyreact.ReactWidget):
    _esm = pathlib.Path("../ipyreact/test.jsx")
// test.jsx
import confetti from "https://esm.sh/canvas-confetti@1.6.0";
import { useState } from "react";
export function Main() {
    const [count, setCount] = useState(0);
    return <button onClick={() => confetti() && setCount(count + 1)}>{count} times confetti</button>;
};

Note that I use babel to "importmap" the import { useState } from "react"; import line into the correct expression.
Also note that this does not require any bundler/nodejs process.

If there is interest in this approach in anywidget, I'm happy to discuss this further.

@maartenbreddels
Copy link

This led to https://github.com/widgetti/ipyreact
Enjoy :)

Repository owner locked and limited conversation to collaborators Sep 17, 2023
@manzt manzt converted this issue into discussion #289 Sep 17, 2023
@manzt manzt unpinned this issue Sep 17, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants