intendednull / yewdux

Ergonomic state management for Yew applications
https://intendednull.github.io/yewdux/
Apache License 2.0
322 stars 31 forks source link

How do I use this? #34

Closed mike-lloyd03 closed 2 years ago

mike-lloyd03 commented 2 years ago

I'm trying to create a store for popping up notifications to the user. But trying to follow the docs does not work for me.

From https://intendednull.github.io/yewdux/example.html:

use yew::prelude::*;
use yewdux::prelude::*;

#[derive(Debug, Default, Clone, PartialEq, Eq, Store)]
struct Counter {
    count: u32,
}
...

But when I implement this, I get the following error:

error: cannot find derive macro `Store` in this scope
 --> crates/frontend/src/components/notifier.rs:5:48
  |
5 | #[derive(Debug, Clone, Default, PartialEq, Eq, Store)]
  |                                                ^^^^^
  |
note: `Store` is imported here, but it is only a trait, without a derive macro

I see there is a yewdux-functional crate on crates.io but this isn't mentioned anywhere in the yewdux docs. Using the example from docs.rs seems to work.

use yew::prelude::*;
use yewdux::prelude::*;
use yewdux_functional::use_store;

#[derive(Clone, Default, PartialEq, Eq)]
pub struct Notification {
    pub msg: Option<String>,
    pub lvl: Option<String>,
}

#[function_component(Notifier)]
pub fn notifier() -> Html {
    let store = use_store::<BasicStore<Notification>>();
    let msg = store.state().map(|s| s.msg).unwrap_or(None);
    let lvl = store
        .state()
        .map(|s| s.lvl.unwrap_or_else(|| "".to_string()))
        .unwrap();
    let dismiss = store.dispatch().reduce_callback(|_| Notification {
        ..Default::default()
    });

    match msg {
        Some(message) => html! {
            <div class="container">
                <div
                    class={match lvl.as_str() {
                        "warn" => "alert alert-warning",
                        "error" =>  "alert alert-danger",
                        _ => "alert alert-info",
                    }}
                    onclick={dismiss}
                    role="alert">
                    {message}
                </div>
            </div>
        },
        None => html! {},
    }
}

But I'm not sure how to update this state from another component. In the example below, if the form submission received a 400 response from the request to the backend, I'd like to display a notification in the <Notifier /> component which is rendered at the top of the tree.

#[function_component(NewKey)]
pub fn new_key() -> Html {
    let name = use_state(|| "".to_string());
    let description = use_state(|| "".to_string());
    let store = use_store::<BasicStore<Notification>>();

    let onsubmit = {
        let history = use_history().unwrap();
        let name = name.clone();
        let description = description.clone();

        Callback::once(move |e: FocusEvent| {
            e.prevent_default();
            wasm_bindgen_futures::spawn_local(async move {
                let resp = Request::post("api/keys")
                    ...;

                match resp.status() {
                    400 => {
                        store.state().unwrap().msg = Some("Failed".to_string());
                        store.state().unwrap().lvl = Some("error".to_string());
                    }
                    _ => history.push(Route::Keys),
                }
            });
        })
    };

    html!{
    ...
    }
}

But the compiler tells me cannot assign to data in an 'Rc'. Any guidance would be appreciated and I can open a PR to update the docs once I understand what I should do here.

Thank you.

mike-lloyd03 commented 2 years ago

Okay I think I figured out the second part of this.

match resp.status() {
                    400 => {
                        store.dispatch().reduce(|s| {
                            s.msg = Some("Failed".to_string());
                            s.lvl = Some("error".to_string());
                        })
                    }
                    _ => history.push(Route::Keys),j

But some clarification on the first error would be greatly appreciated.

intendednull commented 2 years ago

Apologies, this is my fault. What version are you using? I've been meaning to add a disclaimer to the book, as it is only relevant to the master branch at the moment. If you need to use latest release, please see the readme and examples on this branch https://github.com/intendednull/yewdux/tree/0.7.0

If you don't have any other dependencies requiring latest Yew 0.19, I suggest using the master branch of both Yew and Yewdux, as there have been many major improvements in Yewdux's features and ergonomics. Just need Yew 0.20 to release in order to release Yewdux 0.8

intendednull commented 2 years ago

Went ahead and released 0.8 (though it's missing a feature). Try using that instead

mike-lloyd03 commented 2 years ago

Awesome thanks for the quick response. I'll update to 0.8 and give it another shot.

mike-lloyd03 commented 2 years ago

Okay I updated to 0.8.1 but I still can't derive the Store trait.

#[derive(Clone, Default, PartialEq, Eq, Store)]
pub struct Notification {
    pub msg: Option<String>,
    pub lvl: Option<String>,
}
trait impl with the same name found

and

the trait bound `components::notifier::Notification: yewdux::store::Store` is not satisfied
perhaps two different versions of crate `yewdux` are being used?

It looks like yewdux-functional is still dependent on yewdux 0.7.0 so cargo is loading both versions and it's causing a conflict.

intendednull commented 2 years ago

Try removing yewdux-functional. It's now merged into yewdux as of 0.8

mike-lloyd03 commented 2 years ago

That did it. Working good now. The ergonomics are a bit better with 0.8 now too. I just cut a hundred or so lines of code out of my project with this. Thanks!