-
Notifications
You must be signed in to change notification settings - Fork 17
feat: Add ui.component memoization and selective re-rendering #1296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
mofojed
wants to merge
17
commits into
deephaven:main
Choose a base branch
from
mofojed:ui-component-memoization
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
4492fd0
Add component memoization implementation plan
mofojed 5e5c404
WIP add ui.memo functionality
mofojed ca3c73b
Fix unit tests
mofojed 16871f1
test: Add unit tests for ui.memo decorator
mofojed 959b3fd
feat: Support @ui.memo without parentheses and add custom compare tests
mofojed 178229a
docs: Add documentation for @ui.memo component memoization
mofojed 60e5fcb
refactor: Replace @ui.memo decorator with memo parameter on @ui.compo…
mofojed 881760d
Change up how children are compared
mofojed 2d868e8
fix: Remove func param from second overload to fix Pylance error
mofojed fb3ee3b
Add plans for selective re-rendering, and tests that currently fail
mofojed 00eb7e8
Selective re-rendering
mofojed c6b048c
Just change the example to require `list`
mofojed 0241cc8
Fix up typing
mofojed fa74c95
Need to mark the RenderContext as dirty when importing state
mofojed 04fd6d1
Update docs snapshots
mofojed b592731
Fix up typing of NodeType and PropsType
mofojed ee1bdbc
Skip a test
mofojed File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
319 changes: 319 additions & 0 deletions
319
plugins/ui/docs/add-interactivity/memoizing-components.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,319 @@ | ||
| # Memoizing Components | ||
|
|
||
| The `memo` parameter on `@ui.component` optimizes component rendering by skipping re-renders when a component's props haven't changed. This is similar to [React.memo](https://react.dev/reference/react/memo) and is useful for improving performance in components that render often with the same props. | ||
|
|
||
| > [!NOTE] | ||
| > The `memo` parameter is for memoizing entire components. To memoize a value or computation within a component, use the [`use_memo`](../hooks/use_memo.md) hook instead. | ||
|
|
||
| ## Basic Usage | ||
|
|
||
| Add `memo=True` to your component to skip re-renders when props are unchanged: | ||
|
|
||
| ```python | ||
| from deephaven import ui | ||
|
|
||
|
|
||
| @ui.component(memo=True) | ||
| def greeting(name): | ||
| print(f"Rendering greeting for {name}") | ||
| return ui.text(f"Hello, {name}!") | ||
|
|
||
|
|
||
| @ui.component | ||
| def app(): | ||
| count, set_count = ui.use_state(0) | ||
|
|
||
| return ui.flex( | ||
| ui.button("Increment", on_press=lambda: set_count(count + 1)), | ||
| ui.text(f"Count: {count}"), | ||
| greeting("World"), # Won't re-render when count changes | ||
| direction="column", | ||
| ) | ||
|
|
||
|
|
||
| app_example = app() | ||
| ``` | ||
|
|
||
| In this example, clicking the button increments `count`, causing `app` to re-render. However, `greeting` will not re-render because its prop (`"World"`) hasn't changed. | ||
|
|
||
| ## How It Works | ||
|
|
||
| By default, when a parent component re-renders, all of its child components re-render too. With `memo=True`, `deephaven.ui` compares the new props with the previous props using shallow equality. If all props are equal, the component skips rendering and reuses its previous result. | ||
|
|
||
| The render cycle with memoization: | ||
|
|
||
| 1. **Trigger**: Parent component state changes | ||
| 2. **Render**: Parent re-renders, but memoized children with unchanged props are skipped | ||
| 3. **Commit**: Only changed parts of the UI are updated | ||
|
|
||
| ## When to Use `memo` | ||
|
|
||
| Use `memo=True` when: | ||
|
|
||
| - A component renders often with the same props | ||
| - A component is expensive to render (complex calculations, many children) | ||
| - A parent component re-renders frequently but passes stable props to children | ||
|
|
||
| Don't use `memo` when: | ||
|
|
||
| - The component's props change on almost every render | ||
| - The component is cheap to render | ||
| - You're prematurely optimizing without measuring performance | ||
|
|
||
| ```python | ||
| from deephaven import ui | ||
|
|
||
|
|
||
| # Good candidate: renders same static content while parent updates | ||
| @ui.component(memo=True) | ||
| def expensive_chart(data): | ||
| # Imagine this does complex data processing | ||
| return ui.text(f"Chart with {len(data)} points") | ||
|
|
||
|
|
||
| # Not a good candidate: props change every render | ||
| @ui.component | ||
| def live_counter(count): | ||
| return ui.text(f"Count: {count}") | ||
|
|
||
|
|
||
| @ui.component | ||
| def dashboard(): | ||
| count, set_count = ui.use_state(0) | ||
| chart_data = [1, 2, 3, 4, 5] # Static data | ||
|
|
||
| return ui.flex( | ||
| ui.button("Update", on_press=lambda: set_count(count + 1)), | ||
| live_counter(count), # No benefit from memo - count always changes | ||
| expensive_chart(chart_data), # Benefits from memo - data is stable | ||
| direction="column", | ||
| ) | ||
|
|
||
|
|
||
| dashboard_example = dashboard() | ||
| ``` | ||
|
|
||
| ## Custom Comparison Function | ||
|
|
||
| By default, `memo=True` uses shallow equality to compare props. You can provide a custom comparison function by passing it directly to `memo`: | ||
|
|
||
| ```python | ||
| from deephaven import ui | ||
|
|
||
|
|
||
| def compare_by_id(prev_props, next_props): | ||
| """Only re-render if the 'id' prop changes.""" | ||
| return prev_props.get("id") == next_props.get("id") | ||
|
|
||
|
|
||
| @ui.component(memo=compare_by_id) | ||
| def user_card(id, name, last_updated): | ||
| return ui.flex( | ||
| ui.text(f"User #{id}"), | ||
| ui.text(f"Name: {name}"), | ||
| ui.text(f"Updated: {last_updated}"), | ||
| direction="column", | ||
| ) | ||
|
|
||
|
|
||
| @ui.component | ||
| def user_profile(): | ||
| name, set_name = ui.use_state("Alice") | ||
| timestamp, set_timestamp = ui.use_state("12:00") | ||
|
|
||
| return ui.flex( | ||
| ui.button("Update timestamp", on_press=lambda: set_timestamp("12:01")), | ||
| ui.button("Change name", on_press=lambda: set_name("Bob")), | ||
| # Only re-renders if id changes, not name or last_updated | ||
| user_card(id=1, name=name, last_updated=timestamp), | ||
| direction="column", | ||
| ) | ||
|
|
||
|
|
||
| user_profile_example = user_profile() | ||
| ``` | ||
|
|
||
| The custom comparison function receives two dictionaries: | ||
|
|
||
| - `prev_props`: The props from the previous render | ||
| - `next_props`: The props for the current render | ||
|
|
||
| Return `True` to skip re-rendering (props are "equal"), or `False` to re-render. | ||
|
|
||
| ### Deep Equality Comparison | ||
|
|
||
| For props containing nested data structures, you might want deep equality: | ||
|
|
||
| ```python | ||
| from deephaven import ui | ||
|
|
||
|
|
||
| def deep_equal(prev_props, next_props): | ||
| """Compare props using deep equality.""" | ||
| import json | ||
|
|
||
| return json.dumps(prev_props, sort_keys=True) == json.dumps( | ||
| next_props, sort_keys=True | ||
| ) | ||
|
|
||
|
|
||
| @ui.component(memo=deep_equal) | ||
| def data_display(config): | ||
| return ui.text(f"Config: {config}") | ||
|
|
||
|
|
||
| @ui.component | ||
| def app(): | ||
| count, set_count = ui.use_state(0) | ||
|
|
||
| return ui.flex( | ||
| ui.button("Increment", on_press=lambda: set_count(count + 1)), | ||
| # Even though a new dict is created each render, deep_equal | ||
| # will detect the values are the same and skip re-rendering | ||
| data_display(config={"setting": "value", "enabled": True}), | ||
| direction="column", | ||
| ) | ||
|
|
||
|
|
||
| app_example = app() | ||
| ``` | ||
|
|
||
| ### Threshold-Based Comparison | ||
|
|
||
| You can implement more sophisticated comparison logic: | ||
|
|
||
| ```python | ||
| from deephaven import ui | ||
|
|
||
|
|
||
| def significant_change(prev_props, next_props, threshold=5): | ||
| """Only re-render if value changes by more than threshold.""" | ||
| prev_value = prev_props.get("value", 0) | ||
| next_value = next_props.get("value", 0) | ||
| return abs(next_value - prev_value) <= threshold | ||
|
|
||
|
|
||
| @ui.component(memo=significant_change) | ||
| def progress_bar(value): | ||
| return ui.progress_bar(value=value, label=f"{value}%") | ||
|
|
||
|
|
||
| @ui.component | ||
| def app(): | ||
| value, set_value = ui.use_state(0) | ||
|
|
||
| return ui.flex( | ||
| ui.button("+1", on_press=lambda: set_value(value + 1)), | ||
| ui.button("+10", on_press=lambda: set_value(value + 10)), | ||
| # Only re-renders when value changes by more than 5 | ||
| progress_bar(value=value), | ||
| direction="column", | ||
| ) | ||
|
|
||
|
|
||
| app_example = app() | ||
| ``` | ||
|
|
||
| ## Syntax Options | ||
|
|
||
| The `memo` parameter accepts different values: | ||
|
|
||
| ```python skip-test | ||
| # Memoization disabled (default behavior) | ||
| @ui.component | ||
| def my_component(prop): | ||
| return ui.text(prop) | ||
|
|
||
|
|
||
| # Memoization with shallow comparison | ||
| @ui.component(memo=True) | ||
| def my_memoized_component(prop): | ||
| return ui.text(prop) | ||
|
|
||
|
|
||
| # Memoization with custom comparison function | ||
| @ui.component(memo=my_custom_compare) | ||
| def my_component_custom(prop): | ||
| return ui.text(prop) | ||
| ``` | ||
|
|
||
| ## Common Pitfalls | ||
|
|
||
| ### Creating New Objects in Props | ||
|
|
||
| When you pass a new object, list, or dictionary as a prop, it will always be a different reference, causing re-renders even if the content is the same: | ||
|
|
||
| ```python | ||
| from deephaven import ui | ||
|
|
||
|
|
||
| @ui.component(memo=True) | ||
| def item_list(items): | ||
| return ui.flex(*[ui.text(item) for item in items], direction="column") | ||
|
|
||
|
|
||
| @ui.component | ||
| def app(): | ||
| count, set_count = ui.use_state(0) | ||
|
|
||
| # BAD: Creates a new list on every render | ||
| # item_list will re-render every time even though content is the same | ||
| items_bad = ["apple", "banana"] | ||
|
|
||
| # GOOD: Use use_memo to keep the same reference | ||
| items_good = ui.use_memo(lambda: ["apple", "banana"], []) | ||
|
|
||
| return ui.flex( | ||
| ui.button("Increment", on_press=lambda: set_count(count + 1)), | ||
| ui.text(f"Count: {count}"), | ||
| item_list(items_good), # Won't re-render unnecessarily | ||
| direction="column", | ||
| ) | ||
|
|
||
|
|
||
| app_example = app() | ||
| ``` | ||
|
|
||
| ### Passing Callback Functions | ||
|
|
||
| Lambda functions and inline function definitions create new references each render: | ||
|
|
||
| ```python | ||
| from deephaven import ui | ||
|
|
||
|
|
||
| @ui.component(memo=True) | ||
| def button_row(on_click): | ||
| return ui.button("Click me", on_press=on_click) | ||
|
|
||
|
|
||
| @ui.component | ||
| def app(): | ||
| count, set_count = ui.use_state(0) | ||
|
|
||
| # BAD: Creates a new function reference every render | ||
| # handle_click_bad = lambda: print("clicked") | ||
|
|
||
| # GOOD: Use use_callback to memoize the function | ||
| handle_click_good = ui.use_callback(lambda: print("clicked"), []) | ||
|
|
||
| return ui.flex( | ||
| ui.button("Increment", on_press=lambda: set_count(count + 1)), | ||
| button_row(on_click=handle_click_good), # Won't re-render unnecessarily | ||
| direction="column", | ||
| ) | ||
|
|
||
|
|
||
| app_example = app() | ||
| ``` | ||
|
|
||
| ## Comparison with `use_memo` | ||
|
|
||
| | Feature | `memo` parameter | `use_memo` | | ||
| | ------- | ----------------------------- | ---------------------- | | ||
| | Purpose | Skip re-rendering a component | Cache a computed value | | ||
| | Usage | Parameter on `@ui.component` | Hook inside component | | ||
| | Input | Component props | Dependencies array | | ||
| | Output | Memoized component | Memoized value | | ||
|
|
||
| Use `memo=True` on `@ui.component` to optimize component rendering. Use `use_memo` to optimize expensive calculations within a component. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
plugins/ui/docs/snapshots/223e056fae1e8cc660d6084b4784f2b0.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"file":"add-interactivity/memoizing-components.md","objects":{"dashboard_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb0"},"children":"Update"}},{"__dhElemName":"__main__.live_counter","props":{"children":{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Count: 0"],"slot":"text"}}}},{"__dhElemName":"__main__.expensive_chart","props":{"children":{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Chart with 5 points"],"slot":"text"}}}}]}}},"__dhElemName":"__main__.dashboard"},"state":"{\"state\": {\"0\": 0}}"}}}} |
1 change: 1 addition & 0 deletions
1
plugins/ui/docs/snapshots/22c1e3a013e6a776b2c33e48d0d067e6.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"file":"add-interactivity/memoizing-components.md","objects":{"app_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb0"},"children":"Increment"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Count: 0"],"slot":"text"}},{"__dhElemName":"__main__.greeting","props":{"children":{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Hello, World!"],"slot":"text"}}}}]}}},"__dhElemName":"__main__.app"},"state":"{\"state\": {\"0\": 0}}"}},":log":{"type":"Log","data":"Rendering greeting for World\n"}}} |
1 change: 1 addition & 0 deletions
1
plugins/ui/docs/snapshots/5672d3207b0dce60fc3ae5dd631dad16.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"file":"add-interactivity/memoizing-components.md","objects":{"app_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb0"},"children":"Increment"}},{"__dhElemName":"__main__.data_display","props":{"children":{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Config: {'setting': 'value', 'enabled': True}"],"slot":"text"}}}}]}}},"__dhElemName":"__main__.app"},"state":"{\"state\": {\"0\": 0}}"}}}} |
1 change: 1 addition & 0 deletions
1
plugins/ui/docs/snapshots/649f7ec080e8937474ffc4fe90bfde3d.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"file":"add-interactivity/memoizing-components.md","objects":{"app_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb0"},"children":"Increment"}},{"__dhElemName":"__main__.button_row","props":{"children":{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb1"},"children":"Click me"}}}}]}}},"__dhElemName":"__main__.app"},"state":"{\"state\": {\"0\": 0}}"}}}} |
1 change: 1 addition & 0 deletions
1
plugins/ui/docs/snapshots/7f5367fb55911f4082b4bd2d6587245a.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"file":"add-interactivity/memoizing-components.md","objects":{"app_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb0"},"children":"+1"}},{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb1"},"children":"+10"}},{"__dhElemName":"__main__.progress_bar","props":{"children":{"__dhElemName":"deephaven.ui.components.ProgressBar","props":{"size":"L","labelPosition":"top","label":"0%","value":0,"minValue":0,"maxValue":100}}}}]}}},"__dhElemName":"__main__.app"},"state":"{\"state\": {\"0\": 0}}"}}}} |
1 change: 1 addition & 0 deletions
1
plugins/ui/docs/snapshots/a76f40f8cab7652d92c2112ff43ca309.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"file":"add-interactivity/render-cycle.md","objects":{"parent_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb0"},"children":"Increment"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Count: 0"],"slot":"text"}},{"__dhElemName":"__main__.expensive_child","props":{"children":{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Value: hello"],"slot":"text"}}}}]}}},"__dhElemName":"__main__.parent"},"state":"{\"state\": {\"0\": 0}}"}}}} |
1 change: 1 addition & 0 deletions
1
plugins/ui/docs/snapshots/e2ff6d692f17f9ed9305c25a3c6fea28.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"file":"add-interactivity/memoizing-components.md","objects":{"app_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb0"},"children":"Increment"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Count: 0"],"slot":"text"}},{"__dhElemName":"__main__.item_list","props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["apple"],"slot":"text"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["banana"],"slot":"text"}}]}}}}]}}},"__dhElemName":"__main__.app"},"state":"{\"state\": {\"0\": 0}}"}}}} |
1 change: 1 addition & 0 deletions
1
plugins/ui/docs/snapshots/f38b10993d650f63b27d1e584c3307f7.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"file":"add-interactivity/memoizing-components.md","objects":{"user_profile_example":{"type":"deephaven.ui.Element","data":{"document":{"props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb0"},"children":"Update timestamp"}},{"__dhElemName":"deephaven.ui.components.Button","props":{"variant":"accent","style":"fill","type":"button","onPress":{"__dhCbid":"cb1"},"children":"Change name"}},{"__dhElemName":"__main__.user_card","props":{"children":{"__dhElemName":"deephaven.ui.components.Flex","props":{"direction":"column","gap":"size-100","flex":"auto","children":[{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["User #1"],"slot":"text"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Name: Alice"],"slot":"text"}},{"__dhElemName":"deephaven.ui.components.Text","props":{"children":["Updated: 12:00"],"slot":"text"}}]}}}}]}}},"__dhElemName":"__main__.user_profile"},"state":"{\"state\": {\"0\": \"Alice\", \"1\": \"12:00\"}}"}}}} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent in presentation, line is left in, vs commented out