Creating a React/Redux JupyterLab Extension
In this blog post, I’d like to demonstrate how to create a demo JupyterLab extension that leverages React and Redux, along with TypeScript.
This is what we’ll end up with:
But how do we get there?
Documentation of React-enabled JupyterLab widgets is scarce and not always up to date. I hope to rectify that with this post where we’re going to do the following:
- Get started setting up our dev environment..
- Run
cookiecutter
to create the basic structure and dependencies of a new extension. NOTE— You don’t need to complete this step if you’re using my repository, this is already done for you. - Set up React-specific parts of the app
Of course, if you’d rather learn by reading code, just go ahead and skip to my GitHub repository!
Getting Started
Note: Many of these steps are directly pulled from this tutorial on creating JupyterLab extensions. If you’re unfamiliar with JupyterLab Extensions, this tutorial is a great resource!
If you don’t use conda
, you can achieve similar results with other virtual environment managers. See here for other installation methods.
We’re going to use conda
to manage all of our Python dependencies.
We’re also going to create a new conda
environment called jl-extension-env
.
Run the following command in your terminal:
conda create -n jl-extension-env -c conda-forge jupyterlab cookiecutter nodejs git
We can activate and deactivate this environment like so (we’ll automate this using iTerm2 in a moment).
# Activate this environment when working on this repository
conda activate jl-extension-env
# Deactivate it otherwise
conda deactivate
Running cookiecutter
Note: skip this step if you’re just copying my repository as a starting point. This has already been done for you.
Run the following command wherever you’d like to create your new project.
cookiecutter https://github.com/jupyterlab/extension-cookiecutter-ts
Answer the prompts and then navigate into the newly created directory.
Installing the Extension Locally
Let’s install dependencies and link our development version of the extension with JupyterLab.
# Install dependencies and build the Typescript source
yarn && yarn build
# Link your development version of the extension with JupyterLab
jupyter labextension link .
You can watch the source directory for changes. Use the following commands.
yarn watch
jupyter lab — watch
Or, try my script to automatically open two tabs and run watch mode in both of them.
#!/bin/sh# Replace this with the path to your repository
REPOSITORY_DIR=/Users/davisford/Documents/sample-react-jupyterlab-extension# Replace with the name of your conda environment
CONDA_ENV=jl-extension-env# Helper function.
# Opens a new tab and executes a command in iTerm2# Credit: https://apple.stackexchange.com/questions/110778/open-new-tab-in-iterm-and-execute-command-therenewtabi(){osascript \
-e ‘tell application “iTerm2” to tell current window to set newWindow to (create tab with default profile)’\
-e “tell application \”iTerm2\” to tell current session of newWindow to write text \”${@}\””
}# Open a new tab and start running the local web dev server
newtabi “conda activate ${CONDA_ENV} && cd ${REPOSITORY_DIR} && yarn watch”# Open another new tab and open up JupyterLab
newtabi “conda activate ${CONDA_ENV} && cd ${REPOSITORY_DIR} && jupyter lab — watch”
view rawjupyterlab_dev.sh hosted with ❤ by GitHub
Working with React and Redux
Okay, so the gist of how a JupyterLab extension works is this…
There is an extension
object that runs everything (located in index.ts
). A MainAreaWidget
is created, and we will attach our React app to that widget by extending JupyterLab’s ReactWidget
.
We create a new class called ReactAppWidget
, which extends ReactWidget
. We override the render
function and set up our <Provider>
(which will handle our Redux store), and we render our <AppComponent>
.
import React from ‘react’
import { ReactWidget } from ‘@jupyterlab/apputils’
import { Provider } from ‘react-redux’
import store from ‘../ducks/store’export class ReactAppWidget extends ReactWidget {
constructor() {
super()
}render(): JSX.Element {
return (
<Provider store={store}>
<AppComponent />
</Provider>
)
}
}// Write all of your React here
const AppComponent = (): JSX.Element => {
return (
<div>
{/* Your app here */}
</div>
)
}
view rawApp.tsx hosted with ❤ by GitHub
Note: I haven’t experimented with Context
yet in the JupyterLab setting, but theoretically you would wrap your ContextProvider
around <AppComponent />
here as usual.
That’s pretty much it! <AppComponent />
can be any valid React that you’d like — this render
function is similar to a normal React app’s index.tsx
entry point.
I’ve used redux-toolkit to manage the application’s store — if you’re not familiar with redux-toolkit, it is essentially the CreateReactApp of Redux. It provides a set of boilerplate best practices to get your project hooked up and running quickly. I personally love this library.
Go forth and develop!
You’re all ready to go! Start both watch
modes, and you’re in business!
Once JupyterLab opens in your browser, click the “Commands” icon, and search for “Sample React Redux Extension”.
You’ll then see this neat app!
Congratulations! You’re ready to begin working on your very own React/Redux JupyterLab extension!
I hope this guide has been helpful. If you have any issues getting this to work, please open an issue on my GitHub repository.
Troubleshooting and Other Issues
There is some funky behavior related to closing and re-opening the extension in the same session.
- It is probably 100% my fault. I’ll fix the repo if/when I figure it out.
- For now, this is left as an exercise for the reader :)
Sometimes watch
mode doesn’t seem to refresh very well. Try manually refreshing your page in the browser after making changes.
- If that doesn’t work, just close your JupyterLab tabs and re-open them,
If you get build errors when uninstalling the extension locally, try unlinking it as well
- Use this:
jupyter labextension unlink [your-extension-name]
Originally published at https://daviseford.com on April 24, 2020.
DISCLOSURE STATEMENT: © 2020 Capital One. Opinions are those of the individual author. Unless noted otherwise in this post, Capital One is not affiliated with, nor endorsed by, any of the companies mentioned. All trademarks and other intellectual property used or displayed are property of their respective owners.