Bind widgets to query params - st.radio & st.selectbox#13922
Bind widgets to query params - st.radio & st.selectbox#13922mayagbarnes merged 5 commits intodevelopfrom
st.radio & st.selectbox#13922Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
✅ PR preview is ready!
|
There was a problem hiding this comment.
Pull request overview
This pull request adds query parameter binding functionality to st.radio and st.selectbox widgets, enabling two-way synchronization between widget values and URL query parameters. This feature was already available for other widgets like st.checkbox, st.text_input, and st.color_picker, and this PR extends it to selection widgets.
Changes:
- Added
bindparameter tost.radioandst.selectboxwith support for"query-params"value - Implemented URL seeding, validation, and auto-clearing for invalid options
- Added comprehensive test coverage at all levels (Python unit tests, TypeScript unit tests, and E2E tests)
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| proto/streamlit/proto/Radio.proto | Added query_param_key protobuf field (field 14) to enable frontend-backend communication for query param binding |
| proto/streamlit/proto/Selectbox.proto | Added query_param_key protobuf field (field 14) for selectbox query param binding |
| lib/streamlit/elements/widgets/radio.py | Added bind parameter, set query_param_key in proto when bound, and pass validation options to register_widget |
| lib/streamlit/elements/widgets/selectbox.py | Added bind parameter with proper handling of accept_new_options edge case for validation |
| lib/streamlit/runtime/state/session_state.py | Enhanced _seed_widget_from_url with validation logic for selection widgets using formatted_options |
| lib/streamlit/runtime/state/common.py | Updated formatted_options docstring to clarify its role in URL validation |
| lib/tests/streamlit/elements/radio_test.py | Added comprehensive unit tests for bind parameter functionality |
| lib/tests/streamlit/elements/selectbox_test.py | Added unit tests covering bind parameter with format_func, index=None, and accept_new_options |
| lib/tests/streamlit/runtime/state/session_state_test.py | Added tests for formatted_options validation logic |
| lib/tests/streamlit/typing/radio_types.py | Added type assertion tests for bind parameter |
| lib/tests/streamlit/typing/selectbox_types.py | Added type assertion tests ensuring proper return type inference with bind |
| frontend/lib/src/components/widgets/Radio/Radio.tsx | Configured queryParamBinding and passed to useBasicWidgetState hook |
| frontend/lib/src/components/widgets/Selectbox/Selectbox.tsx | Implemented queryParamBinding configuration matching Radio pattern |
| frontend/lib/src/components/widgets/Radio/Radio.test.tsx | Added tests for query param registration, unregistration, and clearable behavior |
| frontend/lib/src/components/widgets/Selectbox/Selectbox.test.tsx | Added frontend unit tests for query param binding lifecycle |
| e2e_playwright/st_radio.py | Added bound radio widgets (normal, formatted, clearable) to test script |
| e2e_playwright/st_radio_test.py | Added 8 E2E tests covering seeding, URL updates, invalid values, format_func, and clearable behavior |
| e2e_playwright/st_selectbox.py | Added bound selectbox widgets for E2E testing |
| e2e_playwright/st_selectbox_test.py | Added 8 E2E tests with comprehensive coverage including cross-widget pollution guards |
SummaryThis PR adds the
Code QualityThe code is well-structured and follows existing patterns consistently. Both Minor issue — incorrect "Next" comment in proto: In The The Test CoverageTest coverage is thorough and well-organized:
E2E tests correctly use the Backwards CompatibilityThe changes are fully backwards compatible:
Security & RiskNo security concerns identified. The implementation correctly validates URL input:
AccessibilityNo accessibility impact. The changes are purely data-binding logic — no new UI elements, DOM changes, or interaction patterns are introduced. The underlying Recommendations
VerdictAPPROVED: Clean, well-tested implementation that consistently follows established patterns for query param binding. The only issue is a minor off-by-one in a proto comment. This is an automated AI review by |
lukasmasuch
left a comment
There was a problem hiding this comment.
LGTM 👍 with one aspect that might allow some simplification.
| # The list of valid formatted option strings for selection widgets. | ||
| # When set, _seed_widget_from_url validates URL values against this list and | ||
| # rejects any that aren't valid options (e.g., ?foo=invalid). Widgets with a fixed set | ||
| # of options (radio, selectbox) pass this; widgets that accept arbitrary values (selectbox | ||
| # with accept_new_options=True) should not, since any string is valid. |
There was a problem hiding this comment.
One aspect to check: As part of the dynamic options support project, I added some logic that checks if the selected option(s) in the widget state are also available in the list of available options. If the option in state is not part of the available options, it gets automatically reset to the default in session state, return value and frontend. Maybe this logic is enough to handle this case here without requiring the additional check. But not sure on this.
There was a problem hiding this comment.
I did see the validate_and_sync_value_with_options addition - though I think this part is complementary, not duplicative.
validate_and_sync_value_with_options correctly resets the widget value and session state but has no knowledge of query params — it doesn't clean up the URL. Without this formatted_options check in _seed_widget_from_url, an invalid URL param like ?foo=invalid would persist indefinitely.
Adds the bind parameter to `st.radio` & `st.selectbox` to enable two-way sync between widget values and URL query parameters. **Key changes:** - Added `bind="query-params"` support to `st.radio` and `st.selectbox` - Added centralized validation in `_seed_widget_from_url` to reject invalid URL option strings by checking against `formatted_options`. This ensures that invalid query params (e.g., `?size=Random` for a radio with options `["Small", "Medium", "Large"]`) are removed from the URL and the widget falls back to its default. This replaces the previous TODO on `formatted_options` in `WidgetMetadata` - Clearability is tied to `index=None`: when the widget is registered with no default selection, an empty URL param (`?key=`) clears the widget to `None`. When `index` is set, empty URL params are ignored. - For `st.selectbox` with `accept_new_options=True`, `formatted_options` is intentionally not passed, so any string value from the URL is accepted. - `format_func` is handled naturally: the URL contains formatted option strings (what the user sees), and validation checks against the same formatted list.
Describe your changes
Adds the bind parameter to
st.radio&st.selectboxto enable two-way sync between widget values and URL query parameters.Key changes:
bind="query-params"support tost.radioandst.selectbox_seed_widget_from_urlto reject invalid URL option strings by checking againstformatted_options. This ensures that invalid query params (e.g.,?size=Randomfor a radio with options["Small", "Medium", "Large"]) are removed from the URL and the widget falls back to its default. This replaces the previous TODO onformatted_optionsinWidgetMetadataindex=None: when the widget is registered with no default selection, an empty URL param (?key=) clears the widget toNone. Whenindexis set, empty URL params are ignored.st.selectboxwithaccept_new_options=True,formatted_optionsis intentionally not passed, so any string value from the URL is accepted.format_funcis handled naturally: the URL contains formatted option strings (what the user sees), and validation checks against the same formatted list.Testing Plan