intendednull / yewdux

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

Add support for scoped contexts #48

Closed intendednull closed 10 months ago

intendednull commented 1 year ago

Primarily important for SSR support, containing state to the scope of the app, instead of the thread.

For a basic example, you can now create a local context for your dispatch:

#[derive(Clone, PartialEq, Default, Store)]
struct Counter(u32);

let cx = yewdux::Context::new();
let dispatch = Dispatch::<Counter>::new(&cx);

Changes to one context are not reflected in any others:

let cx_1 = yewdux::Context::new();
let dispatch_1 = Dispatch::<Counter>::new(&cx_1);

let cx_2 = yewdux::Context::new();
let dispatch_2 = Dispatch::<Counter>::new(&cx_2);

dispatch_1.set(Counter(1));
dispatch_2.set(Counter(2));

assert_ne!(dispatch_1.get(), dispatch_2.get());

The default context for a dispatch is global (thread local).

let dispatch_1 = Dispatch::<Counter>::global();
// This is equivalent to above.
let dispatch_2 = Dispatch::<Counter>::new(&yewdux::Context::global());

dispatch_1.set(Counter(1));

assert_eq!(dispatch_1.get(), dispatch_2.get());

A root component is now available to provide context that is scoped to the app:

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

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

#[function_component]
fn Counter() -> Html {
    let (state, dispatch) = use_store::<State>();
    let onclick = dispatch.reduce_mut_callback(|state| state.count += 1);
    html! {
        <>
        <p>{ state.count }</p>
        <button {onclick}>{"+1"}</button>
        </>
    }
}

#[function_component]
fn App() -> Html {
    html! {
        <YewduxRoot>
            <Counter />
        </YewduxRoot>
    }
}

Hooks will detect the context provided by YewduxRoot. If no root is provided, it uses global by default.

Roba1993 commented 1 year ago

I just went by this PR and I really like it and would also help me for specific cases. I could use them instead of the yew OOTB use_store hooks, which unfortunately have no reducer functions....

Ebenso I like the new feature I'm strongly argue to not change the actual api for the Dispatcher. This would break my code at 300+ places... I mean this function:

let dispatch = Dispatch::<Counter>::new(&cx);

I would rather propose to keep the new function as it is and have an additional function like this:

let dispatch = Dispatch::<Counter>::ctx(&cx);

I also don't understand the benefit of an App vs Thread context. But this is definitely on me.

I hope, this feedback helps :)

intendednull commented 11 months ago

@Roba1993 thanks for the feedback!

I like the new feature I'm strongly argue to not change the actual api for the Dispatcher. This would break my code at 300+ places...

Hopefully you can refactor easily with a little regex magic. We aren't 1.0 yet, so breaking changes are to be expected, and shouldn't get in the way of improving the API.

I think this new naming scheme makes it super clear to the user when exactly they are accessing global context. It's a topic that may introduce confusion to newcomers, and so vitally important we make it as easy to understand as possible.

Roba1993 commented 11 months ago

Hopefully you can refactor easily with a little regex magic. We aren't 1.0 yet, so breaking changes are to be expected, and shouldn't get in the way of improving the API.

I think this new naming scheme makes it super clear to the user when exactly they are accessing global context. It's a topic that may introduce confusion to newcomers, and so vitally important we make it as easy to understand as possible.

Yes, I will be able to do this 😄