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

Add a method to create derived signals based on an Option<T> signal #2357

Open
ChocolateLoverRaj opened this issue Feb 23, 2024 · 2 comments

Comments

@ChocolateLoverRaj
Copy link

Is your feature request related to a problem? Please describe.
When I first tried to create a conditionally rendered input element, I ran into the issue of recreating the input element each time its value changes.

use leptos::*;

#[component]
pub fn Input() -> impl IntoView {
    let (name, set_name) = create_signal(Some("Controlled".to_string()));

    view! {
        {move || {
            name.get()
                .map(|name| {
                    view! {
                        <input
                            type="text"
                            on:input=move |ev| {
                                set_name(Some(event_target_value(&ev)));
                            }

                            // the `prop:` syntax lets you update a DOM property,
                            // rather than an attribute.
                            prop:value=name
                        />
                    }
                })
        }}
    }
}

This is currently the right way to do it (from #2350):

#[component]
pub fn Input() -> impl IntoView {
    let (name, set_name) = create_signal(Some("Controlled".to_string()));

    view! {
        <input
            type="text"
            on:input=move |ev| {
                set_name(Some(event_target_value(&ev)));
            }
            prop:value=move || name().unwrap_or_default()
        />
        <hr/>
        <pre>{move || format!("{:?}", name())}</pre>
        <button on:click=move |_| set_name(None)>"Clear"</button>
    }
}

However, it's inconvenient to have to call unwrap() because the Rust built-in match isn't used.

Describe the solution you'd like
There may be a better way of doing this but this makes it super convenient while working and following normal Rust control flow:

trait CreateSignalOptionExt<T> {
    fn create_signal_option(self) -> Option<Signal<T>>;
}

impl<T: Clone> CreateSignalOptionExt<T> for ReadSignal<Option<T>> {
    fn create_signal_option(self) -> Option<Signal<T>> {
        let is_some = create_memo(move |_| self.get().is_some());
        match is_some() {
            true => Some(Signal::derive(move || self.get().unwrap())),
            false => None,
        }
    }
}

#[component]
fn App() -> impl IntoView {
    let (name, set_name) = create_signal(Some("Controlled".to_string()));

    view! {
        {move || match name.create_signal_option() {
            Some(name) => {
                view! {
                    <input
                        type="text"
                        on:input=move |ev| {
                            set_name(Some(event_target_value(&ev)));
                        }
                        prop:value=name
                    />
                    <p>"Name is: " {name}</p>
                }.into_view()
            }
            None => {
                view! {
                    <>No name</>
                }.into_view()
            }
        }}
    }
}

I also made a impl function for reading and writing which is nice to use:

trait CreateSignalGetSetOptionExt<T, G: SignalGet<Value = T>, S: SignalSet<Value = T>> {
    fn create_signal_get_set_option(self) -> Option<(G, S)>;
}

impl<
        T: Clone,
        R: SignalGet<Value = Option<T>> + Copy + 'static,
        S: SignalSet<Value = Option<T>> + Copy + 'static,
    > CreateSignalGetSetOptionExt<T, Signal<T>, SignalSetter<T>> for (R, S)
{
    fn create_signal_get_set_option(self) -> Option<(Signal<T>, SignalSetter<T>)> {
        let is_some = create_memo({ move |_| self.0.get().is_some() });
        match is_some() {
            true => Some((
                Signal::derive({ move || self.0.get().unwrap() }),
                SignalSetter::map({ move |n| self.1.set(Some(n)) }),
            )),
            false => None,
        }
    }
}

impl<T: Clone> CreateSignalGetSetOptionExt<T, Signal<T>, SignalSetter<T>> for RwSignal<Option<T>> {
    fn create_signal_get_set_option(self) -> Option<(Signal<T>, SignalSetter<T>)> {
        self.split().create_signal_get_set_option()
    }
}

which can be used like this:

#[component]
fn App() -> impl IntoView {
    let name_signal = create_rw_signal(Some("Controlled".to_string()));

    Fragment::new(vec![
        (move || match name_signal.create_signal_get_set_option() {
            Some((name, set_name)) => Fragment::new(vec![
                input()
                    .attr("type", "text")
                    .on(ev::input, move |ev| {
                        set_name(event_target_value(&ev));
                    })
                    .prop("value", name)
                    .into_view(),
                p().child(
                    Fragment::new(vec!["Name is: ".into_view(), name.into_view()]).into_view(),
                )
                .into_view(),
            ])
            .into_view(),
            None => "Not editing name".into_view(),
        })
        .into_view(),
        button()
            .on(ev::click, move |_| {
                name_signal.update(|name| {
                    *name = match name {
                        Some(_) => None,
                        None => Some("Initial".into()),
                    }
                })
            })
            .child("Toggle")
            .into_view(),
    ])
}

Describe alternatives you've considered
Using <Show /> or doing something similar to what <Show /> does.

Additional context
Based on #2350 (comment)

@rakshith-ravi
Copy link
Contributor

Does this help?

https://leptos-use.rs/utilities/is_some.html

@ChocolateLoverRaj
Copy link
Author

The is_some function you linked only creates a derived signal that calls is_some on an option. It can save a few characters of code, maybe 1 line of code in some situations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants