Created
July 13, 2021 23:35
-
-
Save lambertbrady/3b70a860b1ee6118d141f477c914e5c2 to your computer and use it in GitHub Desktop.
Pyodide React Component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
Doesn't work
Uncaught TypeError: Cannot destructure property 'pyodide' of '(0 , react__WEBPACK_IMPORTED_MODULE_0__.useContext)(...)' as it is undefined.