How to display panel components from react?

I am trying to use the example from the documentation but from a react component, instead of from pure html. I have set up a minimal NextJS react app which can be cloned, and ran locally simply with npm i && npm start. My example works for simple python code returning a string or number, but when I attempt to use it with the example panel code for a simple slider I am unsure what to return in order for react to render the slider.

The python code is contained in src/App.js. I am simply overwriting the myPythonCodeString variable from the panel code to a simple 1+9 arithmetic to demonstrate it works in that simple case.

Any help would be much appreciated.

I also posted the question to stackoverflow and got an answer that was helpful in resolving the issue.

There were 2 issues, one was that the scripts needed to be loaded synchronously, one after another in sequence, I did this using the useScript hook from the usehooks-ts library. The other was that I needed to create a div with an id matching that of the servable target in the panel component in the python code.

A github repo with the working app with the corrections in can be viewed here

The component which runs the python code with pyodide looks like so:

import React, { useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import {useScript} from 'usehooks-ts'

/**
 * Pyodide component
 *
 * @param {object} props - react props
 * @param {string} props.pythonCode - python code to run
 * @param {string} [props.loadingMessage] - loading message
 * @param {string} [props.evaluatingMessage] - evaluating message
 * @returns {object} - pyodide node displaying result of python code
 */
function Pyodide({
  pythonCode,
  loadingMessage = "loading…",
  evaluatingMessage = "evaluating…",
}) {
  const pyodideStatus = useScript(`https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js`, {
    removeOnUnmount: false,
  })
  const bokehStatus = useScript(`https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.js`, {
      removeOnUnmount: false, shouldPreventLoad: pyodideStatus !== "ready"
  })
  const bokehWidgetsStatus = useScript(`https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js`, {
    removeOnUnmount: false, shouldPreventLoad: bokehStatus !== "ready"
  })
  const bokehTablesStatus = useScript(`https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js`, {
    removeOnUnmount: false, shouldPreventLoad: bokehWidgetsStatus !== "ready"
  })
  const panelStatus = useScript(`https://cdn.jsdelivr.net/npm/@holoviz/panel@0.14.0/dist/panel.min.js`, {
    removeOnUnmount: false, shouldPreventLoad: bokehTablesStatus !== "ready"
  })

  console.log(pyodideStatus, bokehStatus, bokehWidgetsStatus, bokehTablesStatus, panelStatus);

  const indexURL = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/";
  const pyodide = useRef(null);
  const [isPyodideLoading, setIsPyodideLoading] = useState(true);
  const [pyodideOutput, setPyodideOutput] = useState(evaluatingMessage); // load pyodide wasm module and initialize it

  useEffect(() => {
    if (panelStatus === "ready") {
      setTimeout(()=>{
        (async function () {
          pyodide.current = await globalThis.loadPyodide({ indexURL });
          setIsPyodideLoading(false);
        })();
      }, 1000)
    }
  }, [pyodide, panelStatus]); // evaluate python code with pyodide and set output

  useEffect(() => {
    if (!isPyodideLoading) {
      const evaluatePython = async (pyodide, pythonCode) => {
        try {
          await pyodide.loadPackage("micropip");
          const micropip = pyodide.pyimport("micropip");
          await micropip.install("panel");
          return await pyodide.runPython(pythonCode);
        } catch (error) {
          console.error(error);
          return "Error evaluating Python code. See console for details.";
        }
      };
      (async function () {
        setPyodideOutput(await evaluatePython(pyodide.current, pythonCode));
      })();
    }
  }, [isPyodideLoading, pyodide, pythonCode]);

  if (panelStatus !== "ready") {
    return <div></div>
  }

  return (
    <>
      <div>
        {isPyodideLoading ? loadingMessage : pyodideOutput}
      </div>
    </>
  );
}

Pyodide.propTypes = {
  pythonCode: PropTypes.string.isRequired,
  loadingMessage: PropTypes.string,
  evaluatingMessage: PropTypes.string
};

export default Pyodide;

And example usage looks like:

import Pyodide from "./pyodide";
import "./styles.css";

let myPythonCodeString = `
import panel as pn
pn.extension(sizing_mode="stretch_width")

slider = pn.widgets.FloatSlider(start=0, end=10, name='Amplitude')

def callback(new):
    return f'Amplitude is: {new}'

component = pn.Row(slider, pn.bind(callback, slider))
component.servable(target='my_panel_widget');
`;

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <Pyodide pythonCode={myPythonCodeString} />
      <div id="my_panel_widget"></div>
    </div>
  );
}
1 Like

The resulting webpage looks like so:

A web page displaying a title and a slider to select an amplitude

2 Likes

Hi @AshleySetter

Welcome to the community.

Thanks so much for taking the take to post and help build the community knowledgebase.

1 Like