Skip to content

Commit

Permalink
Collection of minor changes (#63)
Browse files Browse the repository at this point in the history
* various fixes to forms page

* add docs to `with!` macros

* show full path of `SubmitEvent`
  • Loading branch information
blorbb authored Feb 13, 2024
1 parent 6d9d138 commit 5d23a7e
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/reactivity/working_with_signals.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Instead, you can use the `with!` macro to get references to all the signals at t
let name = move || with!(|first, middle, last| format!("{first} {middle} {last}"));
```

This expands to the same thing as above. Take a look at the `with!` docs for more info, and the corresponding macros `update!`, `with_value!` and `update_value!`.
This expands to the same thing as above. Take a look at the [`with!`](https://docs.rs/leptos/latest/leptos/macro.with.html) docs for more info, and the corresponding macros [`update!`](https://docs.rs/leptos/latest/leptos/macro.update.html), [`with_value!`](https://docs.rs/leptos/latest/leptos/macro.with_value.html) and [`update_value!`](https://docs.rs/leptos/latest/leptos/macro.update_value.html).

## Making signals depend on each other

Expand Down
86 changes: 48 additions & 38 deletions src/view/05_forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,31 +75,51 @@ view! {
In an "uncontrolled input," the browser controls the state of the input element.
Rather than continuously updating a signal to hold its value, we use a
[`NodeRef`](https://docs.rs/leptos/latest/leptos/struct.NodeRef.html) to access
the input once when we want to get its value.
the input when we want to get its value.
In this example, we only notify the framework when the `<form>` fires a `submit`
event.
In this example, we only notify the framework when the `<form>` fires a `submit` event.
Note the use of the [`leptos::html`](https://docs.rs/leptos/latest/leptos/html/index.html#) module, which provides a bunch of types for every HTML element.
```rust
let (name, set_name) = create_signal("Uncontrolled".to_string());
let input_element: NodeRef<Input> = create_node_ref();
let input_element: NodeRef<html::Input> = create_node_ref();
view! {
<form on:submit=on_submit> // on_submit defined below
<input type="text"
value=name
node_ref=input_element
/>
<input type="submit" value="Submit"/>
</form>
<p>"Name is: " {name}</p>
}
```
The view should be pretty self-explanatory by now. Note two things:

1. Unlike in the controlled input example, we use `value` (not `prop:value`).
This is because we’re just setting the initial value of the input, and letting
the browser control its state. (We could use `prop:value` instead.)
2. We use `node_ref=...` to fill the `NodeRef`. (Older examples sometimes use `_ref`.
They are the same thing, but `node_ref` has better rust-analyzer support.)

`NodeRef` is a kind of reactive smart pointer: we can use it to access the
underlying DOM node. Its value will be set when the element is rendered.

```rust
let on_submit = move |ev: SubmitEvent| {
let on_submit = move |ev: leptos::ev::SubmitEvent| {
// stop the page from reloading!
ev.prevent_default();

// here, we'll extract the value from the input
let value = input_element()
// event handlers can only fire after the view
// is mounted to the DOM, so the `NodeRef` will be `Some`
.expect("<input> to exist")
// `NodeRef` implements `Deref` for the DOM element type
.expect("<input> should be mounted")
// `leptos::HtmlElement<html::Input>` implements `Deref`
// to a `web_sys::HtmlInputElement`.
// this means we can call`HtmlInputElement::value()`
// to get the current value of the input
.value();
Expand All @@ -109,33 +129,15 @@ let on_submit = move |ev: SubmitEvent| {

Our `on_submit` handler will access the input’s value and use it to call `set_name`.
To access the DOM node stored in the `NodeRef`, we can simply call it as a function
(or using `.get()`). This will return `Option<web_sys::HtmlInputElement>`, but we
know it will already have been filled when we rendered the view, so it’s safe to
unwrap here.
(or using `.get()`). This will return `Option<leptos::HtmlElement<html::Input>>`, but we
know that the element has already been mounted (how else did you fire this event!), so
it's safe to unwrap here.

We can then call `.value()` to get the value out of the input, because `NodeRef`
gives us access to a correctly-typed HTML element.

```rust
view! {
<form on:submit=on_submit>
<input type="text"
value=name
node_ref=input_element
/>
<input type="submit" value="Submit"/>
</form>
<p>"Name is: " {name}</p>
}
```

The view should be pretty self-explanatory by now. Note two things:

1. Unlike in the controlled input example, we use `value` (not `prop:value`).
This is because we’re just setting the initial value of the input, and letting
the browser control its state. (We could use `prop:value` instead.)
2. We use `node_ref` to fill the `NodeRef`. (Older examples sometimes use `_ref`.
They are the same thing, but `node_ref` has better rust-analyzer support.)
Take a look at [`web_sys` and `HtmlElement`](../web_sys.md) to learn more about using a `leptos::HtmlElement`.
Also see the full CodeSandbox example at the end of this page.

## Special Cases: `<textarea>` and `<select>`

Expand All @@ -144,36 +146,36 @@ Two form elements tend to cause some confusion, in different ways.
### `<textarea>`

Unlike `<input>`, the `<textarea>` element does not support a `value` attribute.
Instead, it receives its value as a plain text node in its HTML children,
Instead, it receives its value as a plain text node in its HTML children.

In the current version of Leptos (in fact in Leptos 0.1-0.5), creating a dynamic child
In the current version of Leptos (in fact in Leptos 0.1-0.6), creating a dynamic child
inserts a comment marker node. This can cause incorrect `<textarea>` rendering (and issues
during hydration) if you try to use it to show dynamic content.

Instead, you can pass a non-reactive initial value as a child, and use `prop:value` to
set its current value. (`<textarea>` doesn’t support the `value` attribute, but _does_
support the `value` property...)
set its current value. (`<textarea>` doesn’t support the `value` **attribute**, but _does_
support the `value` **property**...)

```rust
view! {
<textarea
prop:value=move || some_value.get()
on:input=/* etc */
>
/* untracked, plain-text initial value */
{untrack(move || some_value.get())}
/* plain-text initial value, does not change if the signal changes */
{move || some_value.get_untracked()}
</textarea>
}
```

### `<select>`

The `<select>` element also does not have a `value` attribute, _or_ a `value` property.
The `<select>` element also does not have a `value` attribute, _nor_ a `value` property.
Instead, its value is determined by the `selected` attribute of its `<option>`
fields. Some frameworks obscure this with a `value` field on `<select>`; if you try this
in Leptos (or vanilla JavaScript) it won’t work.

Instead, use the `selected` field:
To use the `selected` field:

```rust
let (value, set_value) = create_signal("B".to_string());
Expand All @@ -199,6 +201,7 @@ view! {
```

That's somewhat repetitive, but can easily be refactored:

```rust
#[component]
pub fn App() -> impl IntoView {
Expand Down Expand Up @@ -228,6 +231,13 @@ pub fn SelectOption(is: &'static str, value: ReadSignal<String>) -> impl IntoVie
}
```

> Tip: the single `value` attribute in the component is equivalent to `value=value`.
> This is only the case for _components_: in HTML elements, a single `value` attribute is equivalent to `value=true`.
> This is expected to be made consistent in the next major version of Leptos; see [this issue](https://github.com/leptos-rs/leptos/issues/2196)
> for more details.
**Controlled vs uncontrolled forms CodeSandbox:**

[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/5-forms-0-5-rf2t7c?file=%2Fsrc%2Fmain.rs%3A1%2C1)

<iframe src="https://codesandbox.io/p/sandbox/5-forms-0-5-rf2t7c?file=%2Fsrc%2Fmain.rs%3A1%2C1" width="100%" height="1000px" style="max-height: 100vh"></iframe>
Expand Down

0 comments on commit 5d23a7e

Please sign in to comment.