Skip to content

Commit 439e3aa

Browse files
[feat] Upgrade templates and Cookiecutter to utilize Vite, modern React + TypeScript versions (#102)
* Update README, dependencies, and some formatting * Move to Vite * Undo unnecessary changes * Fixes for build to match prior * Better Type Safety * Update comments * Cookiecutter react template updates * template-reactless upgrade to Vite * Update cookiecutter for frontend-reactless * Undo unnecessary changes * Update vite config * Update template * Update all example package.json * Update MaterialLogin * Update CustomDataframe * Update RadioButton * Update github action infra * Comments * Update MaterialLogin tests * Undo unnecessary changes * Remove vite-tsconfig-paths * Fix up Vite env port handling * Reactless fix * Reactless fix
1 parent 1db79ae commit 439e3aa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+10049
-96052
lines changed
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
# Run the component's dev server on :3001
22
# (The Streamlit dev server already runs on :3000)
3-
PORT=3001
4-
5-
# Don't automatically open the web browser on `npm run start`.
6-
BROWSER=none
3+
VITE_PORT=3001

examples/CustomDataframe/custom_dataframe/frontend/public/index.html renamed to cookiecutter/{{ cookiecutter.package_name }}/{{ cookiecutter.import_name }}/frontend-react/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<meta name="viewport" content="width=device-width, initial-scale=1" />
77
<meta name="theme-color" content="#000000" />
88
<meta name="description" content="Streamlit Component" />
9-
<link rel="stylesheet" href="bootstrap.min.css" />
9+
<link rel="stylesheet" href="/bootstrap.min.css" />
1010
</head>
1111
<body>
1212
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -21,5 +21,6 @@
2121
To begin the development, run `npm start` or `yarn start`.
2222
To create a production bundle, use `npm run build` or `yarn build`.
2323
-->
24+
<script type="module" src="/src/index.tsx"></script>
2425
</body>
2526
</html>

cookiecutter/{{ cookiecutter.package_name }}/{{ cookiecutter.import_name }}/frontend-react/package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
"name": "{{ cookiecutter.import_name }}",
33
"version": "0.1.0",
44
"private": true,
5+
"type": "module",
56
"dependencies": {
6-
"react": "^16.13.1",
7-
"react-dom": "^16.13.1",
7+
"react": "^18.3.1",
8+
"react-dom": "^18.3.1",
89
"streamlit-component-lib": "^2.0.0"
910
},
1011
"scripts": {
11-
"start": "react-scripts start",
12-
"build": "react-scripts build",
13-
"test": "react-scripts test",
14-
"eject": "react-scripts eject"
12+
"start": "vite",
13+
"build": "tsc && vite build"
1514
},
1615
"eslintConfig": {
1716
"extends": "react-app"
@@ -30,10 +29,11 @@
3029
},
3130
"homepage": ".",
3231
"devDependencies": {
33-
"@types/node": "^12.0.0",
34-
"@types/react": "^16.9.0",
35-
"@types/react-dom": "^16.9.0",
36-
"react-scripts": "^5.0.1",
37-
"typescript": "^4.2.0"
32+
"@types/node": "^22.14.1",
33+
"@types/react": "^18.3.20",
34+
"@types/react-dom": "^18.3.6",
35+
"@vitejs/plugin-react-swc": "^3.9.0",
36+
"typescript": "^5.8.3",
37+
"vite": "^6.2.6"
3838
}
3939
}

cookiecutter/{{ cookiecutter.package_name }}/{{ cookiecutter.import_name }}/frontend-react/src/MyComponent.tsx

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,92 @@ import {
33
withStreamlitConnection,
44
ComponentProps,
55
} from "streamlit-component-lib"
6-
import React, { useCallback, useEffect, useMemo, useState, ReactElement } from "react"
6+
import React, {
7+
useCallback,
8+
useEffect,
9+
useMemo,
10+
useState,
11+
ReactElement,
12+
} from "react"
713

814
/**
9-
* This is a React-based component template. The passed props are coming from the
10-
* Streamlit library. Your custom args can be accessed via the `args` props.
15+
* A template for creating Streamlit components with React
16+
*
17+
* This component demonstrates the essential structure and patterns for
18+
* creating interactive Streamlit components, including:
19+
* - Accessing props and args sent from Python
20+
* - Managing component state with React hooks
21+
* - Communicating back to Streamlit via Streamlit.setComponentValue()
22+
* - Using the Streamlit theme for styling
23+
* - Setting frame height for proper rendering
24+
*
25+
* @param {ComponentProps} props - The props object passed from Streamlit
26+
* @param {Object} props.args - Custom arguments passed from the Python side
27+
* @param {string} props.args.name - Example argument showing how to access Python-defined values
28+
* @param {boolean} props.disabled - Whether the component is in a disabled state
29+
* @param {Object} props.theme - Streamlit theme object for consistent styling
30+
* @returns {ReactElement} The rendered component
1131
*/
1232
function MyComponent({ args, disabled, theme }: ComponentProps): ReactElement {
33+
// Extract custom arguments passed from Python
1334
const { name } = args
1435

36+
// Component state
1537
const [isFocused, setIsFocused] = useState(false)
1638
const [numClicks, setNumClicks] = useState(0)
1739

40+
/**
41+
* Dynamic styling based on Streamlit theme and component state
42+
* This demonstrates how to use the Streamlit theme for consistent styling
43+
*/
1844
const style: React.CSSProperties = useMemo(() => {
1945
if (!theme) return {}
2046

21-
// Use the theme object to style our button border. Alternatively, the
22-
// theme style is defined in CSS vars.
47+
// Use the theme object to style the button border
48+
// Access theme properties like primaryColor, backgroundColor, etc.
2349
const borderStyling = `1px solid ${isFocused ? theme.primaryColor : "gray"}`
2450
return { border: borderStyling, outline: borderStyling }
2551
}, [theme, isFocused])
2652

53+
/**
54+
* Tell Streamlit the height of this component
55+
* This ensures the component fits properly in the Streamlit app
56+
*/
2757
useEffect(() => {
28-
Streamlit.setComponentValue(numClicks)
29-
}, [numClicks])
30-
31-
// setFrameHeight should be called on first render and evertime the size might change (e.g. due to a DOM update).
32-
// Adding the style and theme here since they might effect the visual size of the component.
33-
useEffect(() => {
58+
// Call this when the component's size might change
3459
Streamlit.setFrameHeight()
60+
// Adding the style and theme as dependencies since they might
61+
// affect the visual size of the component.
3562
}, [style, theme])
3663

37-
/** Click handler for our "Click Me!" button. */
64+
/**
65+
* Click handler for the button
66+
* Demonstrates how to update component state and send data back to Streamlit
67+
*/
3868
const onClicked = useCallback((): void => {
39-
setNumClicks((prevNumClicks) => prevNumClicks + 1)
40-
}, [])
69+
const newNumClicks = numClicks + 1
70+
// Update local state
71+
setNumClicks(newNumClicks)
72+
// Send value back to Streamlit (will be available in Python)
73+
Streamlit.setComponentValue(newNumClicks)
74+
}, [numClicks])
4175

42-
/** Focus handler for our "Click Me!" button. */
76+
/**
77+
* Focus handler for the button
78+
* Updates visual state when the button receives focus
79+
*/
4380
const onFocus = useCallback((): void => {
4481
setIsFocused(true)
4582
}, [])
4683

47-
/** Blur handler for our "Click Me!" button. */
84+
/**
85+
* Blur handler for the button
86+
* Updates visual state when the button loses focus
87+
*/
4888
const onBlur = useCallback((): void => {
4989
setIsFocused(false)
5090
}, [])
5191

52-
// Show a button and some text.
53-
// When the button is clicked, we'll increment our "numClicks" state
54-
// variable, and send its new value back to Streamlit, where it'll
55-
// be available to the Python program.
5692
return (
5793
<span>
5894
Hello, {name}! &nbsp;
@@ -69,9 +105,13 @@ function MyComponent({ args, disabled, theme }: ComponentProps): ReactElement {
69105
)
70106
}
71107

72-
// "withStreamlitConnection" is a wrapper function. It bootstraps the
73-
// connection between your component and the Streamlit app, and handles
74-
// passing arguments from Python -> Component.
75-
//
76-
// You don't need to edit withStreamlitConnection (but you're welcome to!).
108+
/**
109+
* withStreamlitConnection is a higher-order component (HOC) that:
110+
* 1. Establishes communication between this component and Streamlit
111+
* 2. Passes Streamlit's theme settings to your component
112+
* 3. Handles passing arguments from Python to your component
113+
* 4. Handles component re-renders when Python args change
114+
*
115+
* You don't need to modify this wrapper unless you need custom connection behavior.
116+
*/
77117
export default withStreamlitConnection(MyComponent)
Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
import React from "react"
2-
import ReactDOM from "react-dom"
1+
import React, { StrictMode } from "react"
2+
import { createRoot } from "react-dom/client"
33
import MyComponent from "./MyComponent"
44

5-
ReactDOM.render(
6-
<React.StrictMode>
5+
const rootElement = document.getElementById("root")
6+
7+
if (!rootElement) {
8+
throw new Error("Root element not found")
9+
}
10+
11+
const root = createRoot(rootElement)
12+
13+
root.render(
14+
<StrictMode>
715
<MyComponent />
8-
</React.StrictMode>,
9-
document.getElementById("root")
16+
</StrictMode>
1017
)

cookiecutter/{{ cookiecutter.package_name }}/{{ cookiecutter.import_name }}/frontend-react/src/react-app-env.d.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

cookiecutter/{{ cookiecutter.package_name }}/{{ cookiecutter.import_name }}/frontend-react/tsconfig.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"compilerOptions": {
3-
"target": "es5",
3+
"target": "ESNext",
44
"lib": ["dom", "dom.iterable", "esnext"],
55
"allowJs": true,
66
"skipLibCheck": true,
@@ -13,7 +13,9 @@
1313
"resolveJsonModule": true,
1414
"isolatedModules": true,
1515
"noEmit": true,
16-
"jsx": "react"
16+
"noFallthroughCasesInSwitch": true,
17+
"jsx": "react-jsx",
18+
"types": ["vite/client"]
1719
},
1820
"include": ["src"]
1921
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { defineConfig, loadEnv, UserConfig } from "vite"
2+
import react from "@vitejs/plugin-react-swc"
3+
4+
/**
5+
* Vite configuration for Streamlit React Component development
6+
*
7+
* @see https://vitejs.dev/config/ for complete Vite configuration options
8+
*/
9+
export default defineConfig(({ mode }) => {
10+
const env = loadEnv(mode, process.cwd())
11+
12+
const port = env.VITE_PORT ? parseInt(env.VITE_PORT) : 3001
13+
14+
return {
15+
base: "./",
16+
plugins: [react()],
17+
server: {
18+
port,
19+
},
20+
build: {
21+
outDir: "build",
22+
},
23+
} satisfies UserConfig
24+
})
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
# Run the component's dev server on :3001
22
# (The Streamlit dev server already runs on :3000)
3-
PORT=3001
4-
5-
# Don't automatically open the web browser on `npm run start`.
6-
BROWSER=none
3+
VITE_PORT=3001

0 commit comments

Comments
 (0)