Skip to content

Commit ca027a6

Browse files
authored
Merge pull request #100 from streamlit/refactor/mycomponent-as-functional
Use functional component instead of class-based for template MyComponent
2 parents d825287 + 0755480 commit ca027a6

File tree

2 files changed

+104
-128
lines changed
  • cookiecutter/{{ cookiecutter.package_name }}/{{ cookiecutter.import_name }}/frontend-react/src
  • template/my_component/frontend/src

2 files changed

+104
-128
lines changed

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

+52-64
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,72 @@
11
import {
22
Streamlit,
3-
StreamlitComponentBase,
43
withStreamlitConnection,
4+
ComponentProps,
55
} from "streamlit-component-lib"
6-
import React, { ReactNode } from "react"
7-
8-
interface State {
9-
numClicks: number
10-
isFocused: boolean
11-
}
6+
import React, { useCallback, useEffect, useMemo, useState, ReactElement } from "react"
127

138
/**
14-
* This is a React-based component template. The `render()` function is called
15-
* automatically when your component should be re-rendered.
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.
1611
*/
17-
class MyComponent extends StreamlitComponentBase<State> {
18-
public state = { numClicks: 0, isFocused: false }
12+
function MyComponent({ args, disabled, theme }: ComponentProps): ReactElement {
13+
const { name } = args
1914

20-
public render = (): ReactNode => {
21-
// Arguments that are passed to the plugin in Python are accessible
22-
// via `this.props.args`. Here, we access the "name" arg.
23-
const name = this.props.args["name"]
15+
const [isFocused, setIsFocused] = useState(false)
16+
const [numClicks, setNumClicks] = useState(0)
2417

25-
// Streamlit sends us a theme object via props that we can use to ensure
26-
// that our component has visuals that match the active theme in a
27-
// streamlit app.
28-
const { theme } = this.props
29-
const style: React.CSSProperties = {}
18+
const style: React.CSSProperties = useMemo(() => {
19+
if (!theme) return {}
3020

31-
// Maintain compatibility with older versions of Streamlit that don't send
32-
// a theme object.
33-
if (theme) {
34-
// Use the theme object to style our button border. Alternatively, the
35-
// theme style is defined in CSS vars.
36-
const borderStyling = `1px solid ${
37-
this.state.isFocused ? theme.primaryColor : "gray"
38-
}`
39-
style.border = borderStyling
40-
style.outline = borderStyling
41-
}
21+
// Use the theme object to style our button border. Alternatively, the
22+
// theme style is defined in CSS vars.
23+
const borderStyling = `1px solid ${isFocused ? theme.primaryColor : "gray"}`
24+
return { border: borderStyling, outline: borderStyling }
25+
}, [theme, isFocused])
4226

43-
// Show a button and some text.
44-
// When the button is clicked, we'll increment our "numClicks" state
45-
// variable, and send its new value back to Streamlit, where it'll
46-
// be available to the Python program.
47-
return (
48-
<span>
49-
Hello, {name}! &nbsp;
50-
<button
51-
style={style}
52-
onClick={this.onClicked}
53-
disabled={this.props.disabled}
54-
onFocus={this._onFocus}
55-
onBlur={this._onBlur}
56-
>
57-
Click Me!
58-
</button>
59-
</span>
60-
)
61-
}
27+
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(() => {
34+
Streamlit.setFrameHeight()
35+
}, [style, theme])
6236

6337
/** Click handler for our "Click Me!" button. */
64-
private onClicked = (): void => {
65-
// Increment state.numClicks, and pass the new value back to
66-
// Streamlit via `Streamlit.setComponentValue`.
67-
this.setState(
68-
prevState => ({ numClicks: prevState.numClicks + 1 }),
69-
() => Streamlit.setComponentValue(this.state.numClicks)
70-
)
71-
}
38+
const onClicked = useCallback((): void => {
39+
setNumClicks((prevNumClicks) => prevNumClicks + 1)
40+
}, [])
7241

7342
/** Focus handler for our "Click Me!" button. */
74-
private _onFocus = (): void => {
75-
this.setState({ isFocused: true })
76-
}
43+
const onFocus = useCallback((): void => {
44+
setIsFocused(true)
45+
}, [])
7746

7847
/** Blur handler for our "Click Me!" button. */
79-
private _onBlur = (): void => {
80-
this.setState({ isFocused: false })
81-
}
48+
const onBlur = useCallback((): void => {
49+
setIsFocused(false)
50+
}, [])
51+
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.
56+
return (
57+
<span>
58+
Hello, {name}! &nbsp;
59+
<button
60+
style={style}
61+
onClick={onClicked}
62+
disabled={disabled}
63+
onFocus={onFocus}
64+
onBlur={onBlur}
65+
>
66+
Click Me!
67+
</button>
68+
</span>
69+
)
8270
}
8371

8472
// "withStreamlitConnection" is a wrapper function. It bootstraps the

template/my_component/frontend/src/MyComponent.tsx

+52-64
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,72 @@
11
import {
22
Streamlit,
3-
StreamlitComponentBase,
43
withStreamlitConnection,
4+
ComponentProps,
55
} from "streamlit-component-lib"
6-
import React, { ReactNode } from "react"
7-
8-
interface State {
9-
numClicks: number
10-
isFocused: boolean
11-
}
6+
import React, { useCallback, useEffect, useMemo, useState, ReactElement } from "react"
127

138
/**
14-
* This is a React-based component template. The `render()` function is called
15-
* automatically when your component should be re-rendered.
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.
1611
*/
17-
class MyComponent extends StreamlitComponentBase<State> {
18-
public state = { numClicks: 0, isFocused: false }
12+
function MyComponent({ args, disabled, theme }: ComponentProps): ReactElement {
13+
const { name } = args
1914

20-
public render = (): ReactNode => {
21-
// Arguments that are passed to the plugin in Python are accessible
22-
// via `this.props.args`. Here, we access the "name" arg.
23-
const name = this.props.args["name"]
15+
const [isFocused, setIsFocused] = useState(false)
16+
const [numClicks, setNumClicks] = useState(0)
2417

25-
// Streamlit sends us a theme object via props that we can use to ensure
26-
// that our component has visuals that match the active theme in a
27-
// streamlit app.
28-
const { theme } = this.props
29-
const style: React.CSSProperties = {}
18+
const style: React.CSSProperties = useMemo(() => {
19+
if (!theme) return {}
3020

31-
// Maintain compatibility with older versions of Streamlit that don't send
32-
// a theme object.
33-
if (theme) {
34-
// Use the theme object to style our button border. Alternatively, the
35-
// theme style is defined in CSS vars.
36-
const borderStyling = `1px solid ${
37-
this.state.isFocused ? theme.primaryColor : "gray"
38-
}`
39-
style.border = borderStyling
40-
style.outline = borderStyling
41-
}
21+
// Use the theme object to style our button border. Alternatively, the
22+
// theme style is defined in CSS vars.
23+
const borderStyling = `1px solid ${isFocused ? theme.primaryColor : "gray"}`
24+
return { border: borderStyling, outline: borderStyling }
25+
}, [theme, isFocused])
4226

43-
// Show a button and some text.
44-
// When the button is clicked, we'll increment our "numClicks" state
45-
// variable, and send its new value back to Streamlit, where it'll
46-
// be available to the Python program.
47-
return (
48-
<span>
49-
Hello, {name}! &nbsp;
50-
<button
51-
style={style}
52-
onClick={this.onClicked}
53-
disabled={this.props.disabled}
54-
onFocus={this._onFocus}
55-
onBlur={this._onBlur}
56-
>
57-
Click Me!
58-
</button>
59-
</span>
60-
)
61-
}
27+
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(() => {
34+
Streamlit.setFrameHeight()
35+
}, [style, theme])
6236

6337
/** Click handler for our "Click Me!" button. */
64-
private onClicked = (): void => {
65-
// Increment state.numClicks, and pass the new value back to
66-
// Streamlit via `Streamlit.setComponentValue`.
67-
this.setState(
68-
prevState => ({ numClicks: prevState.numClicks + 1 }),
69-
() => Streamlit.setComponentValue(this.state.numClicks)
70-
)
71-
}
38+
const onClicked = useCallback((): void => {
39+
setNumClicks((prevNumClicks) => prevNumClicks + 1)
40+
}, [])
7241

7342
/** Focus handler for our "Click Me!" button. */
74-
private _onFocus = (): void => {
75-
this.setState({ isFocused: true })
76-
}
43+
const onFocus = useCallback((): void => {
44+
setIsFocused(true)
45+
}, [])
7746

7847
/** Blur handler for our "Click Me!" button. */
79-
private _onBlur = (): void => {
80-
this.setState({ isFocused: false })
81-
}
48+
const onBlur = useCallback((): void => {
49+
setIsFocused(false)
50+
}, [])
51+
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.
56+
return (
57+
<span>
58+
Hello, {name}! &nbsp;
59+
<button
60+
style={style}
61+
onClick={onClicked}
62+
disabled={disabled}
63+
onFocus={onFocus}
64+
onBlur={onBlur}
65+
>
66+
Click Me!
67+
</button>
68+
</span>
69+
)
8270
}
8371

8472
// "withStreamlitConnection" is a wrapper function. It bootstraps the

0 commit comments

Comments
 (0)