Skip to content
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

docs: Using Hooks #1056

Merged
merged 18 commits into from
Dec 16, 2024
195 changes: 195 additions & 0 deletions plugins/ui/docs/describing/using_hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Using Hooks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid words ending with ing in titles.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


Hooks are 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.

## Built-in Hooks

`Deephaven.ui` has a large number of built-in hooks to help with the development of components. The full documenation for these can be found in the `Hooks` section of the documentation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

link?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no md file for hooks. It is in sidebar.json. Is there a way to link to this? I want to link users the section of the docs where they can access all the use_hook.md files.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an overview file for hooks actually that would be appropriate. ../hooks/overview.md.

dgodinez-dh marked this conversation as resolved.
Show resolved Hide resolved

### Use State Hook

Call `use_state` 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 that is the initial state. It initializes to `None` if this is omitted. The hook returns two values: a state variable and a `set` function that lets you update the state and trigger a re-render.

See the [`use_state`](../hooks/use_state.md) documentation for more detailed information.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of saying See the use_whatever documentation for more detailed info, why not just link to that documentation everywhere you mention use_whatever?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


### Use Memo Hook
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth including details that table operations should typically be memoized with use_memo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to put that in docs for "working with tables".


Call `use_memo` 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. The value is computed once and then stored in the memoized value. The memoized value is returned on subsequent renders until the dependencies change.
dgodinez-dh marked this conversation as resolved.
Show resolved Hide resolved

See the [`use_memo`](../hooks/use_memo.md) documentation for more detailed information.

### Use Effect Hook

Call `use_effect` 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to use_effect here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


```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.

See the [`use_effect`](../hooks/use_effect.md) documentation for more detailed information.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't necessary in every section if you just link to the guide wherever you mention use_effect, use_memo, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


### Use Callback Hook

Call `use_callback` 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.

See the [`use_callback`](../hooks/use_callback.md) documentation for more detailed information.

## Rules for Hooks

Hooks are Python functions, but you need to follow two rules when using them.

### Only Call Hooks at the Top Level
dgodinez-dh marked this conversation as resolved.
Show resolved Hide resolved

Don’t call hooks inside loops, conditions, or nested functions. Instead, always use hooks at the top level of your `deephaven.io` 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.
dgodinez-dh marked this conversation as resolved.
Show resolved Hide resolved

### Only Call Hooks from Components and Custom Hooks

Don’t call hooks from regular Python functions. Instead, you can:

- Call Hooks from `deephaven.io` function components.
dgodinez-dh marked this conversation as resolved.
Show resolved Hide resolved
- Call hooks from custom hooks.

Following this rule ensures that all stateful logic in a component is clearly visible from its source code.

## Building 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 Python function that follows these guidelines:
dgodinez-dh marked this conversation as resolved.
Show resolved Hide resolved

- Custom hooks may call other hooks
- Custom hooks follow the same rules as built-in hooks.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to word this better but it's worded kind of oddly right now.
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()
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related documentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no related documentation for this section.

4 changes: 4 additions & 0 deletions plugins/ui/docs/sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
{
"label": "Your First Component",
"path": "describing/your_first_component.md"
},
{
"label": "Using Hooks",
"path": "describing/using_hooks.md"
}
]
},
Expand Down
Loading