Streamlit Custom Components + Vite + Vanilla JS
Tags: python, streamlit, javascript, vite
Published: 2021-09-23 08:08:20

## Create Component Based On vanilla JS

- From the template folder
- Create new component using vite and add __init__.py code for testing the component
```
$ mkdir vite_vanilla_component
$ cd vite_vanilla_component
$ npm init vite@latest frontend --template vanilla # npm v6 (v7 is different)
$ touch __init__.py # command may be different in Windows
```
- Add in the __init__.py code below
```python
import os
import streamlit.components.v1 as components

_RELEASE = False

if not _RELEASE:
  _component_func = components.declare_component(
    "vite_vanilla_component",
    url="http://localhost:3000", # vite dev server port
  )
else:
  parent_dir = os.path.dirname(os.path.abspath(__file__))
  build_dir = os.path.join(parent_dir, "frontend/dist")
  _component_func = components.declare_component("vite_vanilla_component", path=build_dir)

def my_component(name, key=None):
  component_value = _component_func(name=name, key=key, default=0)
  return component_value

if not _RELEASE:
  import streamlit as st
  st.subheader("Component Test")
  num_clicks = my_component(name = "NameViteVanilla")
  st.markdown("You've clicked %s times!" % int(num_clicks))
```
- install the frontend node libraries, streamlit-component-lib, create vite.config.js
```
$ cd frontend
$ npm i
$ npm i streamlit-component-lib
$ touch vite.config.js
```
- vite.config.js should look like below
```js
export default {
  base: './'
}
```
- replace the content of main.js with the following (based on the reactless-template of the original component-template repo)
```js
import { Streamlit } from "streamlit-component-lib"

const span = document.body.appendChild(document.createElement("span"))
const textNode = span.appendChild(document.createTextNode(""))
const button = span.appendChild(document.createElement("button"))
button.textContent = "Click Me!"

let numClicks = 0
let isFocused = false
button.onclick = function() {
  numClicks += 1
  Streamlit.setComponentValue(numClicks)
}

button.onfocus = function() { isFocused = true }
button.onblur = function() { isFocused = false }

function onRender(event) {
  const data = event.detail
  if (data.theme) {
    const borderStyling = `1px solid var(${
      isFocused ? "--primary-color" : "gray"
    })`
    button.style.border = borderStyling
    button.style.outline = borderStyling
  }
  button.disabled = data.disabled
  let name = data.args["name"]
  textNode.textContent = `Hello, ${name}! ` + String.fromCharCode(160)
  Streamlit.setFrameHeight()
}

Streamlit.events.addEventListener(Streamlit.RENDER_EVENT, onRender)
Streamlit.setComponentReady()
Streamlit.setFrameHeight()
```

### Running the example <a name="running"></a>

From the base directory, navigate to the frontend and serve it from a dev server:

```bash
$ cd template/vite_vanilla_component/frontend
$ npm run dev
```

On a separate terminal, from base directory, navigate to and run the Streamlit app (assuming python environment has been activated):

```bash
$ cd template
$ streamlit run vite_vanilla_component/__init__.py
```

You should see the image below, and clicking the button should increment the count.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zu5shgr28rzv0xexaapn.png)

The component looks truncated. To fix this, set body style in the component to have `margin: 0;`.