Skip to content

Commit 701b004

Browse files
docs: Add docs for use_callback, use_ref, hooks overview page (#1012)
- Added a Hooks overview page detailing some of the rules about hooks, and linking to some of the hooks - Also add the `use_callback` docs page - Closes #808, closes #996, closes #809, closes #1014, closes #1015, closes #1016 --------- Co-authored-by: margaretkennedy <[email protected]>
1 parent 00c2181 commit 701b004

File tree

7 files changed

+370
-0
lines changed

7 files changed

+370
-0
lines changed

plugins/ui/docs/hooks/overview.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Hooks
2+
3+
_Hooks_ let you use state and other deephaven.ui features in your components. Hooks are functions that let you "hook into" state and lifecycle features from function components, encapsulating code and logic to avoid duplication. You can either use the built-in hooks or combine them to build your own.
4+
5+
## Example
6+
7+
```python
8+
from deephaven import ui
9+
10+
11+
@ui.component
12+
def ui_counter():
13+
count, set_count = ui.use_state(0)
14+
return ui.button(f"Pressed {count} times", on_press=lambda: set_count(count + 1))
15+
16+
17+
counter = ui_counter()
18+
```
19+
20+
## UI recommendations
21+
22+
1. **Hooks must be used within components or other hooks**: Hooks require a rendering context, and therefore can only be used within component functions or other hooks. They cannot be used in regular Python functions or outside of components.
23+
2. **All hooks start with `use_`**: For example, `use_state` is a hook that lets you add state to your components.
24+
3. **Hooks must be called at the _top_ level**: Do not use hooks inside loops, conditions, or nested functions. This ensures that hooks are called in the same order each time a component renders. If you want to use one in a conditional or a loop, extract that logic to a new component and put it there.
25+
26+
## Built-in hooks
27+
28+
Below are all the built-in hooks that deephaven.ui provides.
29+
30+
### State hooks
31+
32+
_State_ lets a component remember some data between renders and trigger a re-render when the data changes. For example, a counter component might use state to keep track of the current count.
33+
34+
The [`use_state`](use_state.md) hook adds state to a component.
35+
36+
### Ref hooks
37+
38+
A _ref_ provides a way to hold a value that isn't used for re-rendering. Unlike with state, updating a ref does not re-render your component.
39+
40+
- [`use_ref`](use_ref.md) returns a mutable ref object whose `.current` property is initialized to the passed argument.
41+
42+
### Effect hooks
43+
44+
An _effect_ hook lets you perform side effects in your components; for example, data fetching, setting up a subscription, and manually synchronizing with an external system.
45+
46+
- [`use_effect`](use_effect.md) lets you perform side effects in your components.
47+
48+
### Performance hooks
49+
50+
_Performance_ hooks let you optimize components for performance. They allow you to memoize expensive computations so that you can avoid re-running them on every render, or skip unnecessary re-rendering.
51+
52+
- [`use_memo`](use_memo.md) lets you memoize expensive computations.
53+
- [`use_callback`](use_callback.md) lets you cache a function definition before passing to an effect or child component, preventing unnecessary rendering. It's like `use_memo` but specifically for functions.
54+
55+
### Data hooks
56+
57+
_Data_ hooks let you use data from within a Deephaven table in your component.
58+
59+
- [`use_table_data`](use_table_data.md) lets you use the full table contents.
60+
- [`use_column_data`](use_column_data.md) lets you use the data of one column.
61+
- [`use_cell_data`](use_cell_data.md) lets you use the data of one cell.
62+
63+
## Create custom hooks
64+
65+
You can create your own hooks to reuse stateful logic between components. A custom hook is a JavaScript function whose name starts with `use` and that may call other hooks. For example, let's say you want to create a custom hook that checks whether a table cell is odd. You can create a custom hook called `use_is_cell_odd`:
66+
67+
```python
68+
from deephaven import time_table, ui
69+
70+
71+
def use_is_cell_odd(table):
72+
cell_value = ui.use_cell_data(table, 0)
73+
return cell_value % 2 == 1
74+
75+
76+
@ui.component
77+
def ui_table_odd_cell(table):
78+
is_odd = use_is_cell_odd(table)
79+
return ui.view(f"Is the cell odd? {is_odd}")
80+
81+
82+
_table = time_table("PT1s").update("x=i").view("x").tail(1)
83+
table_odd_cell = ui_table_odd_cell(_table)
84+
```
85+
86+
Notice at the end of our custom hook, we check if the cell value is odd and return the result. We then use this custom hook in our component to display whether the cell is odd.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# use_callback
2+
3+
`use_callback` is a hook that memoizes a callback function, returning the same callback function on subsequent renders when the dependencies have not changed. This is useful when passing callbacks to functions or components that rely on reference equality to prevent unnecessary re-renders or effects from firing.
4+
5+
## Example
6+
7+
```python
8+
from deephaven import ui
9+
import time
10+
11+
12+
@ui.component
13+
def ui_server():
14+
theme, set_theme = ui.use_state("red")
15+
16+
create_server = ui.use_callback(lambda: {"host": "localhost"}, [])
17+
18+
def connect():
19+
server = create_server()
20+
print(f"Connecting to {server}")
21+
time.sleep(0.5)
22+
23+
ui.use_effect(connect, [create_server])
24+
25+
return ui.view(
26+
ui.picker(
27+
"red",
28+
"orange",
29+
"yellow",
30+
label="Theme",
31+
selected_key=theme,
32+
on_change=set_theme,
33+
),
34+
padding="size-100",
35+
background_color=theme,
36+
)
37+
38+
39+
my_server = ui_server()
40+
```
41+
42+
In the example above, `use_callback` memoizes the `create_server` callback. The `connect` function is then passed to [`use_effect`](./use_effect.md) with `create_server` as a dependency. This ensures the effect will not be triggered on every re-render because the `create_server` callback is memoized.
43+
44+
`use_callback` is similar to [`use_memo`](./use_memo.md), but for functions instead of values. Use `use_callback` when you need to memoize a callback function that relies on reference equality to prevent unnecessary re-renders.
45+
46+
## Recommendations
47+
48+
Recommendations for memoizing callback functions:
49+
50+
1. **Use memoization when callbacks are passed into expensive effects**: If the callback is being passed into an expensive `use_effect` or `use_memo` call, use `use_callback` so that it maintains referential equality.
51+
2. **Use dependencies**: Pass in only the dependencies that the memoized callback relies on. If any of the dependencies change, the memoized callback will be re-computed.
52+
53+
## API Reference
54+
55+
```{eval-rst}
56+
.. dhautofunction:: deephaven.ui.use_callback
57+
```
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# use_cell_data
2+
3+
`use_cell_data` lets you use the cell data of the first cell (first row in the first column) in a table. This is useful when you want to listen to an updating table and use the data in your component.
4+
5+
## Example
6+
7+
```python
8+
from deephaven import time_table, ui
9+
10+
11+
@ui.component
12+
def ui_table_first_cell(table):
13+
cell_value = ui.use_cell_data(table)
14+
return ui.heading(f"The first cell value is {cell_value}")
15+
16+
17+
table_first_cell = ui_table_first_cell(time_table("PT1s").tail(1))
18+
```
19+
20+
In the above example, `ui_table_first_cell` is a component that listens to the last row of a table and displays the value of the first cell. The `cell_value` variable is updated every time the table updates.
21+
22+
## Recommendations
23+
24+
1. **Use `use_cell_data` for listening to table updates**: If you need to listen to a table for one cell of data, use `use_cell_data`.
25+
2. **Use table operations to filter to one cell**: Because `use_cell_data` always uses the top-left cell of the table, you can filter your table to determine what cell to listen to. If your table has multiple rows and columns, use table operations such as `.where` and `.select` to filter to the desired cell.
26+
27+
## API Reference
28+
29+
```{eval-rst}
30+
.. dhautofunction:: deephaven.ui.use_cell_data
31+
```
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# use_column_data
2+
3+
`use_column_data` lets you use the data of the first column of a table. This is useful when you want to listen to an updating table and use the data in your component.
4+
5+
## Example
6+
7+
```python
8+
from deephaven import time_table, ui
9+
10+
11+
@ui.component
12+
def ui_table_column(table):
13+
column_data = ui.use_column_data(table)
14+
return ui.heading(f"The column data is {column_data}")
15+
16+
17+
table_column = ui_table_column(time_table("PT1s").tail(5))
18+
```
19+
20+
In the above example, `ui_table_column` is a component that listens to the last 5 rows of a table and displays the values of the first column. The `column_data` variable is updated every time the table updates.
21+
22+
## Recommendations
23+
24+
1. **Use `use_column_data` for listening to table updates**: If you need to listen to a table for one column of data, use `use_column_data`.
25+
2. **Use table operations to filter to one column**: If your table has multiple rows and columns, use table operations such as `.where` and `.select` to filter to the column you want to listen to. `use_column_data` always uses the first column of the table.
26+
3. **Do not use `use_column_data` with [`list_view`](../components/list_view.md) or [`picker`](../components/picker.md)**: Some components are optimized to work with large tables of data, and will take a table passed in directly as their data source, only pulling in the options currently visible to the user. In those cases, pass the table directly to the component, otherwise you will fetch the entire column of data unnecessarily.
27+
28+
## Tab switcher with `use_column_data`
29+
30+
In the example below, use the `use_column_data` hook to get all the options for the `Exchange` columns, then build tabs for each option. When you click on a tab, the table is filtered to show only the rows where the `Exchange` column matches the tab name.
31+
32+
```python
33+
from deephaven import ui
34+
from deephaven.table import Table
35+
from deephaven.plot import express as dx
36+
37+
38+
@ui.component
39+
def ui_table_tabs(source: Table, column_name: str):
40+
table_options = ui.use_memo(
41+
lambda: source.select_distinct("Exchange"), [source, column_name]
42+
)
43+
exchanges = ui.use_column_data(table_options)
44+
45+
return ui.tabs(
46+
*[
47+
ui.tab(source.where(f"{column_name}=`{exchange}`"), title=exchange)
48+
for exchange in exchanges
49+
]
50+
)
51+
52+
53+
_stocks = dx.data.stocks()
54+
table_tabs = ui_table_tabs(_stocks, "Exchange")
55+
```
56+
57+
## API Reference
58+
59+
```{eval-rst}
60+
.. dhautofunction:: deephaven.ui.use_column_data
61+
```

plugins/ui/docs/hooks/use_ref.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# use_ref
2+
3+
`use_ref` returns a mutable ref object whose `.current` property is initialized to the passed argument. The returned object will persist for the full lifetime of the component. Updating the `.current` property will not trigger a re-render.
4+
5+
## Example
6+
7+
```python
8+
from deephaven import ui
9+
10+
11+
@ui.component
12+
def ui_ref_counter():
13+
ref = ui.use_ref(0)
14+
15+
def handle_press():
16+
ref.current += 1
17+
print(f"You clicked {ref.current} times!")
18+
19+
return ui.button("Click me!", on_press=handle_press)
20+
21+
22+
ref_counter = ui_ref_counter()
23+
```
24+
25+
Note that we're only using the `ref.current` value within the `handle_press` method. This is because updating the ref does not trigger a re-render, so the component will not reflect the updated value. The value will be printed out each time you press the button. If you need to update the component based on the ref value, consider using `use_state` instead.
26+
27+
## Recommendations
28+
29+
1. **Use `use_ref` for mutable values**: If you need to store a mutable value that doesn't affect the component's rendering, use `use_ref`.
30+
2. **Use `use_state` for values that affect rendering**: If you need to store a value that will affect the component's rendering, use `use_state` instead of `use_ref`.
31+
32+
## Stopwatch example
33+
34+
Here's an example of a stopwatch using `use_ref`. Press the **Start** button to start the stopwatch and the **Stop** button to stop it. The elapsed time will be displayed in the text field:
35+
36+
```python
37+
from deephaven import ui
38+
import datetime
39+
from threading import Timer
40+
41+
42+
class RepeatTimer(Timer):
43+
def run(self):
44+
while not self.finished.wait(self.interval):
45+
self.function(*self.args, **self.kwargs)
46+
47+
48+
@ui.component
49+
def ui_stopwatch():
50+
start_time, set_start_time = ui.use_state(datetime.datetime.now())
51+
now, set_now = ui.use_state(start_time)
52+
timer_ref = ui.use_ref(None)
53+
54+
def stop():
55+
if timer_ref.current is not None:
56+
timer_ref.current.cancel()
57+
58+
def start():
59+
stop()
60+
new_start_time = datetime.datetime.now()
61+
set_start_time(new_start_time)
62+
set_now(new_start_time)
63+
timer_ref.current = RepeatTimer(0.01, lambda: set_now(datetime.datetime.now()))
64+
timer_ref.current.start()
65+
66+
return ui.view(
67+
ui.heading(f"Elapsed time: {now - start_time}"),
68+
ui.button("Start", on_press=start),
69+
ui.button("Stop", on_press=stop),
70+
)
71+
72+
73+
stopwatch = ui_stopwatch()
74+
```
75+
76+
## API Reference
77+
78+
```{eval-rst}
79+
.. dhautofunction:: deephaven.ui.use_ref
80+
```
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# use_table_data
2+
3+
`use_table_data` lets you use the data of a table. This is useful when you want to listen to an updating table and use the data in your component.
4+
5+
## Example
6+
7+
```python
8+
from deephaven import time_table, ui
9+
10+
11+
@ui.component
12+
def ui_table_data(table):
13+
table_data = ui.use_table_data(table)
14+
return ui.heading(f"The table data is {table_data}")
15+
16+
17+
table_data = ui_table_data(time_table("PT1s").update("x=i").tail(5))
18+
```
19+
20+
In the above example, `ui_table_data` is a component that listens to the last 5 rows of a table and displays the data. The `table_data` variable is updated every time the table updates.
21+
22+
## Recommendations
23+
24+
1. **Use `use_table_data` for listening to table updates**: If you need to listen to a table for all the data, use `use_table_data`.
25+
2. **Use table operations to filter to the data you want**: If your table has multiple rows and columns, use table operations such as `.where` and `.select` to filter to the data you want to listen to.
26+
27+
## API Reference
28+
29+
```{eval-rst}
30+
.. dhautofunction:: deephaven.ui.use_table_data
31+
```

plugins/ui/docs/sidebar.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,26 @@
210210
{
211211
"label": "Hooks",
212212
"items": [
213+
{
214+
"label": "Overview",
215+
"path": "hooks/overview.md"
216+
},
213217
{
214218
"label": "use_boolean",
215219
"path": "hooks/use_boolean.md"
216220
},
221+
{
222+
"label": "use_callback",
223+
"path": "hooks/use_callback.md"
224+
},
225+
{
226+
"label": "use_cell_data",
227+
"path": "hooks/use_cell_data.md"
228+
},
229+
{
230+
"label": "use_column_data",
231+
"path": "hooks/use_column_data.md"
232+
},
217233
{
218234
"label": "use_effect",
219235
"path": "hooks/use_effect.md"
@@ -222,9 +238,17 @@
222238
"label": "use_memo",
223239
"path": "hooks/use_memo.md"
224240
},
241+
{
242+
"label": "use_ref",
243+
"path": "hooks/use_ref.md"
244+
},
225245
{
226246
"label": "use_state",
227247
"path": "hooks/use_state.md"
248+
},
249+
{
250+
"label": "use_table_data",
251+
"path": "hooks/use_table_data.md"
228252
}
229253
]
230254
}

0 commit comments

Comments
 (0)