Skip to content

Instantly share code, notes, and snippets.

@zhiwehu
Forked from lambertbrady/pyodide-provider.js
Created January 28, 2022 16:50
Show Gist options
  • Select an option

  • Save zhiwehu/db28e87aa950bdf5587948467c5e2585 to your computer and use it in GitHub Desktop.

Select an option

Save zhiwehu/db28e87aa950bdf5587948467c5e2585 to your computer and use it in GitHub Desktop.
Pyodide React Component
import { createContext, useRef, useState } from 'react'
export const PyodideContext = createContext()
export default function PyodideProvider({ children }) {
const pyodide = useRef(null)
const hasLoadPyodideBeenCalled = useRef(false)
const [isPyodideLoading, setIsPyodideLoading] = useState(true)
return (
<PyodideContext.Provider
value={{
pyodide,
hasLoadPyodideBeenCalled,
isPyodideLoading,
setIsPyodideLoading
}}
>
{children}
</PyodideContext.Provider>
)
}
import { useContext, useEffect, useState } from 'react'
import Head from 'next/head'
import { PyodideContext } from './pyodide-provider'
export default function Pyodide({
id,
pythonCode,
loadingMessage = 'loading...',
evaluatingMessage = 'evaluating...'
}) {
const indexURL = 'https://cdn.jsdelivr.net/pyodide/dev/full/'
const {
pyodide,
hasLoadPyodideBeenCalled,
isPyodideLoading,
setIsPyodideLoading
} = useContext(PyodideContext)
const [pyodideOutput, setPyodideOutput] = useState(evaluatingMessage)
// load pyodide wasm module and initialize it
useEffect(() => {
if (!hasLoadPyodideBeenCalled.current) {
// immediately set hasLoadPyodideBeenCalled ref, which is part of context, to true
// this prevents any additional Pyodide components from calling loadPyodide a second time
hasLoadPyodideBeenCalled.current = true
;(async function () {
pyodide.current = await globalThis.loadPyodide({ indexURL })
// updating value of isPyodideLoading triggers second useEffect
setIsPyodideLoading(false)
})()
}
// pyodide and hasLoadPyodideBeenCalled are both refs and setIsPyodideLoading is a setState function (from context)
// as a result, these dependencies will be stable and never cause the component to re-render
}, [pyodide, hasLoadPyodideBeenCalled, setIsPyodideLoading])
// evaluate python code with pyodide and set output
useEffect(() => {
if (!isPyodideLoading) {
const evaluatePython = async (pyodide, pythonCode) => {
try {
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))
})()
}
// component re-renders when isPyodideLoading changes, which is set with first useEffect and updated via context
}, [isPyodideLoading, pyodide, pythonCode])
return (
<>
<Head>
<script src={`${indexURL}pyodide.js`} />
</Head>
<div id={id}>
Pyodide Output: {isPyodideLoading ? loadingMessage : pyodideOutput}
</div>
</>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment