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: Add docs for use_callback, use_ref, hooks overview page #1012

Merged
merged 11 commits into from
Nov 19, 2024
86 changes: 86 additions & 0 deletions plugins/ui/docs/hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Hooks

_Hooks_ let you use state and other deephaven.ui features in your components. They are a way to reuse stateful logic between components. Hooks are functions that let you "hook into" state and lifecycle features from function components. You can either use the built-in hooks or combine them to build your own.
mofojed marked this conversation as resolved.
Show resolved Hide resolved

## Example

```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()
```

## UI recommendations

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.
2. **All hooks start with `use_`**: For example, `use_state` is a hook that lets you add state to your components.
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.

## Built-in hooks

Below are all the built-in hooks that deephaven.ui provides.

### State hooks

_State_ lets a component remember some data between renders. State is a way to preserve data between renders and to trigger a re-render when the data changes. For example, a counter component might use state to keep track of the current count.
mofojed marked this conversation as resolved.
Show resolved Hide resolved

To add state to a component, use the [`use_state`](use_state.md) hook.
mofojed marked this conversation as resolved.
Show resolved Hide resolved

### Ref hooks

_Refs_ provide 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.
mofojed marked this conversation as resolved.
Show resolved Hide resolved

- [`use_ref`](use_ref.md) returns a mutable ref object whose `.current` property is initialized to the passed argument.

### Effect hooks

_Effects_ let you perform side effects in your components. Data fetching, setting up a subscription, and manually synchronizing with an external system are all examples of side effects.
mofojed marked this conversation as resolved.
Show resolved Hide resolved

- [`use_effect`](use_effect.md) lets you perform side effects in your components.

### Performance hooks

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

- [`use_memo`](use_memo.md) lets you memoize expensive computations.
- [`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.

### Data hooks

_Data_ hooks let you use data from within a Deephaven table in your component.

- [`use_table_data`](use_table_data.md) lets you use the full table contents.
- [`use_column_data`](use_column_data.md) lets you use the column data of one column.
mofojed marked this conversation as resolved.
Show resolved Hide resolved
- [`use_cell_data`](use_cell_data.md) lets you use the cell data of one cell.
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have a ticket assigned to anyone to doc these?

Copy link
Member Author

Choose a reason for hiding this comment

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

Created tickets #1014, #1015, #1016 and added to this PR

mofojed marked this conversation as resolved.
Show resolved Hide resolved

## Create custom hooks

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

```python
from deephaven import time_table, ui


def use_is_cell_odd(table):
cell_value = ui.use_cell_data(table, 0)
return cell_value % 2 == 1


@ui.component
def ui_table_odd_cell(table):
is_odd = use_is_cell_odd(table)
return ui.view(f"Is the cell odd? {is_odd}")


_table = time_table("PT1s").update("x=i").view("x").tail(1)
table_odd_cell = ui_table_odd_cell(_table)
```

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.
57 changes: 57 additions & 0 deletions plugins/ui/docs/hooks/use_callback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# use_callback

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

## Example

```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()
```

In the example above, the `create_server` callback is memoized using `use_callback`. The `connect` function is then passed to [`use_effect`](./use_effect.md) with `create_server` as a dependency. This ensures that the effect will not be triggered on every re-render because the `create_server` callback is memoized.
mofojed marked this conversation as resolved.
Show resolved Hide resolved

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

## Recommendations

Recommendations for memoizing callback functions:

1. **Use memoization when callbacks passed into expensive effects**: If the callback is being passed into an expensive `use_effect` or `use_memo` call, `use_callback` so that it maintains referential equality.
mofojed marked this conversation as resolved.
Show resolved Hide resolved
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.
mofojed marked this conversation as resolved.
Show resolved Hide resolved

## API Reference

```{eval-rst}
.. dhautofunction:: deephaven.ui.use_callback
```
8 changes: 8 additions & 0 deletions plugins/ui/docs/sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,18 @@
{
"label": "Hooks",
"items": [
{
"label": "Overview",
"path": "hooks/README.md"
mofojed marked this conversation as resolved.
Show resolved Hide resolved
},
{
"label": "use_boolean",
"path": "hooks/use_boolean.md"
},
{
"label": "use_callback",
"path": "hooks/use_callback.md"
},
{
"label": "use_effect",
"path": "hooks/use_effect.md"
Expand Down
Loading