-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
https://deephaven.atlassian.net/browse/DOC-205 --------- Co-authored-by: margaretkennedy <82049573+margaretkennedy@users.noreply.github.com> Co-authored-by: Mike Bender <mikebender@deephaven.io>
1 parent
ef7e741
commit 28b5a51
Showing
2 changed files
with
190 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,186 @@ | ||
# Use Hooks | ||
|
||
Hooks are Python functions that isolate reusable parts of a component. Built-in `deephaven.ui` hooks allow you to manage state, cache values, synchronize with external systems, and much more. You can either use the built-in hooks or combine them to build your own. | ||
|
||
## Rules for Hooks | ||
|
||
Hooks are Python functions, but you need to follow two rules when using them. | ||
|
||
1. Only call hooks at the top level. | ||
|
||
Don’t call hooks inside loops, conditions, or nested functions. Instead, always use hooks at the top level of your `deephaven.ui` component function, before any early returns. By following this rule, you ensure that hooks are called in the same order each time a component renders. | ||
|
||
2. Only call hooks from components and custom hooks | ||
|
||
Don’t call hooks from regular Python functions. Instead, you can: | ||
|
||
- Call Hooks from `@ui.component` decorated functions. | ||
- Call hooks from custom hooks. | ||
|
||
Following this rule ensures that all stateful logic in a component is clearly visible from its source code. | ||
|
||
## Built-in Hooks | ||
|
||
`deephaven.ui` has a large number of built-in hooks to help with the development of components. More details can be found in the [`hooks` section](../hooks/overview.md) of the documentation. | ||
|
||
### Use State Hook | ||
|
||
Call [`use_state`](../hooks/use_state.md) at the top level of your component to declare a state variable. | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def ui_counter(): | ||
count, set_count = ui.use_state(0) | ||
return ui.button(f"Pressed {count} times", on_press=lambda: set_count(count + 1)) | ||
|
||
|
||
counter = ui_counter() | ||
``` | ||
|
||
The `use_state` hook takes an optional parameter, the initial state. If this is omitted, it initializes to `None`. The hook returns two values: a state variable and a `set` function that lets you update the state and trigger a re-render. | ||
|
||
### Use Memo Hook | ||
|
||
Call [`use_memo`](../hooks/use_memo.md) to cache the result of a calculation, function, or operation. This is useful when you have a value that is expensive to compute and you want to avoid re-computing it on every render. | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def ui_todo_list(todos: list[str], filter: str): | ||
filtered_todos = ui.use_memo( | ||
lambda: [todo for todo in todos if filter in todo], [todos, filter] | ||
) | ||
|
||
return [ | ||
ui.text(f"Showing {len(filtered_todos)} of {len(todos)} todos"), | ||
*[ui.checkbox(todo) for todo in filtered_todos], | ||
] | ||
|
||
|
||
result = ui_todo_list(["Do grocery shopping", "Walk the dog", "Do laundry"], "Do") | ||
``` | ||
|
||
The `use_memo` hook takes two parameters: a `callable` that returns a value and a list of dependencies. When dependencies are changed, the value is computed once and then stored in the memoized value. The memoized value is returned on subsequent renders until the dependencies change. The memoized value is returned on subsequent renders until the dependencies change. | ||
|
||
### Use Effect Hook | ||
|
||
Call [`use_effect`](../hooks/use_effect.md) to synchronize a component with an external system. An effect runs when it is mounted or a dependency changes. An optional cleanup function runs when dependencies change or the component is unmounted. | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def ui_effect_example(): | ||
def handle_mount(): | ||
# effect prints "Mounted" once when component is first rendered | ||
print("Mounted") | ||
# cleanup function prints "Unmounted" when component is closed | ||
return lambda: print("Unmounted") | ||
|
||
# Passing in an empty list for dependencies will run the effect only once when the component is mounted, and cleanup when the component is unmounted | ||
ui.use_effect(handle_mount, []) | ||
|
||
return ui.text("Effect Example") | ||
|
||
|
||
effect_example = ui_effect_example() | ||
``` | ||
|
||
The `use_effect` hook takes two parameters: a callable and a list of dependencies. The callable may return a function for cleanup. | ||
|
||
### Use Callback Hook | ||
|
||
Call [`use_callback`](../hooks/use_callback.md) to memoize a callback function. This prevents unnecessary re-renders when the dependencies of the callback have not changed. | ||
|
||
```python | ||
from deephaven import ui | ||
import time | ||
|
||
|
||
@ui.component | ||
def ui_server(): | ||
theme, set_theme = ui.use_state("red") | ||
|
||
create_server = ui.use_callback(lambda: {"host": "localhost"}, []) | ||
|
||
def connect(): | ||
server = create_server() | ||
print(f"Connecting to {server}") | ||
time.sleep(0.5) | ||
|
||
ui.use_effect(connect, [create_server]) | ||
|
||
return ui.view( | ||
ui.picker( | ||
"red", | ||
"orange", | ||
"yellow", | ||
label="Theme", | ||
selected_key=theme, | ||
on_change=set_theme, | ||
), | ||
padding="size-100", | ||
background_color=theme, | ||
) | ||
|
||
|
||
my_server = ui_server() | ||
``` | ||
|
||
The `use_callback` hook takes two parameters: a callable and a list of dependencies. It returns a memoized callback. The memoized callback is returned on subsequent renders until the dependencies change. | ||
|
||
## Build your own hooks | ||
|
||
When you have reusable logic involving one or more hooks, you may want to write a custom hook to encapsulate that logic. A hook is a Python function that follows these guidelines: | ||
|
||
- Hooks can call other hooks, but usage of hooks within hooks follows the same rules as using hooks within components. | ||
- Custom hooks should start with the word `use` to indicate that is a hook and may contain component state and effects. | ||
|
||
### Example: Extracting the `use_server` hook | ||
|
||
Look back at the code example for the `use_callback` hook. The component uses two hooks to connect to a server. This logic can be extracted into a `use_server` hook to make it reusable by other components. | ||
|
||
```python | ||
from deephaven import ui | ||
import time | ||
|
||
# Custom hook | ||
def use_server(): | ||
create_server = ui.use_callback(lambda: {"host": "localhost"}, []) | ||
|
||
def connect(): | ||
server = create_server() | ||
print(f"Connecting to {server}") | ||
time.sleep(0.5) | ||
|
||
ui.use_effect(connect, [create_server]) | ||
|
||
|
||
@ui.component | ||
def ui_server(): | ||
theme, set_theme = ui.use_state("red") | ||
|
||
use_server() | ||
|
||
return ui.view( | ||
ui.picker( | ||
"red", | ||
"orange", | ||
"yellow", | ||
label="Theme", | ||
selected_key=theme, | ||
on_change=set_theme, | ||
), | ||
padding="size-100", | ||
background_color=theme, | ||
) | ||
|
||
|
||
my_server = ui_server() | ||
``` |
This file contains 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