Git Product home page Git Product logo

bounce's Introduction

Bounce

Run Tests & Publishing crates.io docs.rs

The uncomplicated state management library for Yew.

Bounce is inspired by Redux and Recoil.

Rationale

Yew state management solutions that are currently available all have some (or all) of the following limitations:

  • Too much boilerplate.

    Users either have to manually control whether to notify subscribers or have to manually define contexts.

  • State change notifies all.

    State changes will notify all subscribers.

  • Needless clones.

    A clone of the state will be produced for all subscribers whenever there's a change.

Bounce wants to be a state management library that:

  • Has minimal boilerplate.

    Changes are automatically detected via PartialEq.

  • Only notifies relevant subscribers.

    When a state changes, only hooks that subscribe to that state will be notified.

  • Reduces Cloning.

    States are Rc'ed.

Example

For bounce states to function, a <BounceRoot /> must be registered.

#[function_component(App)]
fn app() -> Html {
    html! {
        <BounceRoot>
            {children}
        </BounceRoot>
    }
}

A simple state is called an Atom.

You can derive Atom for any struct that implements PartialEq and Default.

#[derive(PartialEq, Atom)]
struct Username {
    inner: String,
}

impl Default for Username {
    fn default() -> Self {
        Self {
            inner: "Jane Doe".into(),
        }
    }
}

You can then use it with the use_atom hook.

When an Atom is first used, it will be initialised with its Default value.

#[function_component(Setter)]
fn setter() -> Html {
    let username = use_atom::<Username>();

    let on_text_input = {
        let username = username.clone();

        Callback::from(move |e: InputEvent| {
            let input: HtmlInputElement = e.target_unchecked_into();

            username.set(Username { inner: input.value().into() });
        })
    };

    html! {
        <div>
            <input type="text" oninput={on_text_input} value={username.inner.to_string()} />
        </div>
    }
}

If you wish to create a read-only (or set-only) handle, you can use use_atom_value (or use_atom_setter).

#[function_component(Reader)]
fn reader() -> Html {
    let username = use_atom_value::<Username>();

    html! { <div>{"Hello, "}{&username.inner}</div> }
}

You can find the full example here.

License

Bounce is dual licensed under the MIT license and the Apache License (Version 2.0).

bounce's People

Contributors

andoriyu avatar dependabot[bot] avatar futursolo avatar kinnison avatar mibes404 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

bounce's Issues

"Error: no bounce root found" when using `use_atom`

when I try to use bounce as follows with a simple use_atom I get the error "Error: No bounce root found". I get this error as long as I have a use_atom line in my app() function, even if I don't use it in the markup.

use bounce::*;
use yew::prelude::*;

#[derive(PartialEq, Clone, Eq, Atom)]
struct SimpleAtom {
    something: bool,
}

impl Default for SimpleAtom {
    fn default() -> Self {
        Self { something: false }
    }
}

#[function_component(App)]
fn app() -> Html {
    let simple = use_atom::<SimpleAtom>();
    html! {
        <BounceRoot>
            <h1>{"hello"}</h1>
            if simple.something {
                <h1>{"more stuff"}</h1>
            }
        </BounceRoot>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

SSR Support for Core API

#13 delivers SSR Support for Helmet API and Query API whilst this issue aims to provide SSR support for the Core API.

This is not included in #13 as SSR is mostly useful with public facing pages which are indexed by SEO.
States in those pages are usually obtainable from client-sided states.

Helmet Reconciliation Strategy

Currently, Bounce Helmet uses a predetermined set of rules to determine whether a tag is a duplicate of an existing render tag.

This can be hard to understand and sometimes users may want to customise this behaviour.

This issue will consider of whether switching it to a Key-based behaviour is simpler to maintain and easier to understand for users.

Suspense Support

This issue aims to implement Suspense support that is coming in Yew 0.20 for the Query API.

Race condition in `notion_states`

If you have a notion created when another notion is applied (I know, weird to have in single threaded env)โ€”you get the following panic:

Stack Trace
panicked at 'already borrowed: BorrowMutError', /root/.cargo/registry/src/github.com-1ecc6299db9ec823/bounce-0.3.0/src/root_state.rs:58:60

Stack:

getImports/imports.wbg.__wbg_new_693216e109162396@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7.js:684:21
console_error_panic_hook::Error::new::ha49ec9af7589f04e@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[52694]:0xbeaca5
console_error_panic_hook::hook_impl::heef5593d14762556@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[4983]:0x590dec
console_error_panic_hook::hook::h6c496aaa23375f03@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[61665]:0xc65185
core::ops::function::Fn::call::h123cfd7f170d7100@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[47180]:0xb94509
std::panicking::rust_panic_with_hook::h84feca33bd4bd229@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[14332]:0x8116d8
std::panicking::begin_panic_handler::{{closure}}::hd2eacd3bb9ff1eab@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[21452]:0x939f0b
std::sys_common::backtrace::__rust_end_short_backtrace::h976699518d897fb1@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[71714]:0xcc5996
rust_begin_unwind@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[57148]:0xc296be
core::panicking::panic_fmt::hc171d095bc4a492d@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[57902]:0xc33b24
core::result::unwrap_failed::h68ab818eb89182b6@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[24717]:0x9a69ea
core::result::Result<T,E>::expect::h63d696e347e87aab@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[17715]:0x8ab5ce
core::cell::RefCell<T>::borrow_mut::h7bf002e59385c374@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[20828]:0x923ba8
bounce::root_state::BounceRootState::get_state::h7f99151b46a42f6f@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[612]:0x24d857
bounce::states::input_selector::use_input_selector_value::{{closure}}::h56a75144902eaa63@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[6872]:0x63b6c8
yew::functional::hooks::use_state::use_state_eq::{{closure}}::h13190c85dbc60cea@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[15696]:0x853287
yew::functional::hooks::use_reducer::use_reducer_base::{{closure}}::h24d55e4df0336239@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[8824]:0x6cf14d
yew::functional::hooks::use_hook::{{closure}}::hbd47b99fc7c7c4dc@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[1909]:0x3d2658
scoped_tls_hkt::ScopedKeyMut<T>::with::{{closure}}::hece77949598b0ce5@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[14972]:0x830ce1
scoped_tls_hkt::ScopedKeyMut<T>::replace::h0f4e7a45a504323d@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[7680]:0x67c30d
scoped_tls_hkt::ScopedKeyMut<T>::with::h68fbc7280ee00cc9@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[15167]:0x83a27d
yew::functional::hooks::use_hook::h05c2ce181bb3e2b0@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[2171]:0x4089e5
yew::functional::hooks::use_reducer::use_reducer_base::heba7247273a3db2b@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[18309]:0x8c4320
yew::functional::hooks::use_reducer::use_reducer_eq::hb239a3237478e890@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[18308]:0x8c4281
yew::functional::hooks::use_state::use_state_eq::h064ee44cd0c6386d@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[6995]:0x645708
bounce::states::input_selector::use_input_selector_value::hd4c1e2af62a53c4d@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[2156]:0x405c31
bounce::query::mutation::use_mutation_value::h24ce079d68aa0f14@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[6893]:0x63d35a
<webui::components::group_list::group_list as yew::functional::FunctionProvider>::run::hc1d649ec5ca39c9e@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[271]:0x15bd64
<yew::functional::FunctionComponent<T> as yew::html::component::Component>::view::{{closure}}::h93e3cd81d26ae7d5@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[45489]:0xb791ac
scoped_tls_hkt::ScopedKeyMut<T>::set::{{closure}}::ha1abacd820faaab0@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[52591]:0xbe9391
scoped_tls_hkt::ScopedKeyMut<T>::replace::hc8f6784df9eb46ba@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[14444]:0x8170f8
scoped_tls_hkt::ScopedKeyMut<T>::set::he07ab51244b7e5c6@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[31202]:0xa5715c
yew::functional::FunctionComponent<T>::with_hook_state::h4bae1d4a7e49b6df@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[14356]:0x812a21
<yew::functional::FunctionComponent<T> as yew::html::component::Component>::view::h0ac87c8bd0f80a85@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[32469]:0xa75dba
<yew::html::component::lifecycle::RenderRunner<COMP> as yew::scheduler::Runnable>::run::hcc9c2b915ffb06e4@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[899]:0x2c0abc
yew::scheduler::start::{{closure}}::h1e9f14a50447b5c6@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[1964]:0x3de003
std::thread::local::LocalKey<T>::try_with::hda46617c7be55e75@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[10958]:0x756e80
std::thread::local::LocalKey<T>::with::hbbd4fe77100b423c@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[30831]:0xa4dd38
yew::scheduler::start::h4e51a1df8ea83d74@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[71767]:0xcc5e22
yew::html::component::scope::Scope<COMP>::push_update::h61ebc5dfa8577a67@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[10983]:0x758530
yew::html::component::scope::Scope<COMP>::send_message::hb2852c741278883e@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[20663]:0x91ddc8
<yew::functional::FunctionComponent<T> as yew::html::component::Component>::create::{{closure}}::h7b13ea75e8cf0350@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[25749]:0x9c4d92
yew::functional::HookUpdater::callback::hcb3872a76eab663b@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[3709]:0x4ff74e
yew::functional::hooks::use_reducer::use_reducer_base::{{closure}}::{{closure}}::h83f78a494f8accbd@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[26052]:0x9cdb83
yew::functional::hooks::use_reducer::UseReducerHandle<T>::dispatch::h718edcc749d7725a@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[16061]:0x863e43
yew::functional::hooks::use_state::UseStateHandle<T>::set::h54822b6f3e7054f1@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[52483]:0xbe796d
bounce::states::input_selector::use_input_selector_value::{{closure}}::{{closure}}::ha85c91a2f7bfc2f6@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[52523]:0xbe8312
yew::callback::Callback<IN>::emit::h54b1999b5520d3b0@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[4867]:0x584ba3
bounce::utils::notify_listeners::h57dddb46aac2586c@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[746]:0x287adc
bounce::states::input_selector::InputSelectorState<T>::notify_listeners::h5a091edb923d15fb@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[38910]:0xb00912
bounce::states::input_selector::InputSelectorState<T>::refresh::h693d59bc8fbe0653@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[972]:0x2da67a
bounce::states::input_selector::InputSelectorState<T>::select_value::{{closure}}::h5191e92310be8d08@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[41246]:0xb2d0f9
yew::callback::Callback<IN>::emit::hf0e9fe0ce9409c09@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[5412]:0x5bbbc6
bounce::root_state::BounceStates::get_slice_value::{{closure}}::hbf5c16f93cde4962@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[30350]:0xa4214d
yew::callback::Callback<IN>::emit::h65105560ab8f1a36@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[4868]:0x584d65
bounce::utils::notify_listeners::h801b0b9688bede70@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[749]:0x288e29
bounce::states::slice::SliceState<T>::notify_listeners::hf927f457daab4267@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[26458]:0x9d97df
<bounce::states::slice::SliceState<T> as bounce::any_state::AnyState>::apply::h1113b9954ec9e65f@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[2392]:0x432959
bounce::root_state::BounceRootState::apply_notion::h7d6cc0494eaa9add@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[2496]:0x445e06
bounce::states::future_notion::use_future_notion_runner::{{closure}}::{{closure}}::hea87e18a117e3eee@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[590]:0x242d07
<core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll::h3d936ed3d9058fa8@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[15808]:0x85842f
wasm_bindgen_futures::task::singlethread::Task::run::h19d41b80f6a80342@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[3546]:0x4ea46a
wasm_bindgen_futures::queue::QueueState::run_all::hc42d2df146d88030@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[2635]:0x45e966
wasm_bindgen_futures::queue::Queue::new::{{closure}}::h81e1122a4df121b3@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[30852]:0xa4e5b5
<dyn core::ops::function::FnMut<(A,)>+Output = R as wasm_bindgen::closure::WasmClosure>::describe::invoke::h2f0f59d65273821e@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[13770]:0x7f5131
__wbg_adapter_39@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7.js:262:10
real@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7.js:228:20
promise callback*getImports/imports.wbg.__wbg_then_18da6e5453572fc8@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7.js:1032:37
js_sys::Promise::then::h41677963cda5bf17@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[29779]:0xa33737
wasm_bindgen_futures::queue::Queue::schedule_task::hb6611f31d39a9b8d@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[6636]:0x628695
wasm_bindgen_futures::queue::Queue::push_task::hd3d0fcba648fdd4d@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[52700]:0xbeae39
wasm_bindgen_futures::task::singlethread::Task::wake_by_ref::{{closure}}::h2d076a185799f58e@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[42295]:0xb40ce6
std::thread::local::LocalKey<T>::try_with::h89228a367f51fd40@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[10456]:0x73971b
std::thread::local::LocalKey<T>::with::h4476390b97da7795@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[28547]:0xa1376a
wasm_bindgen_futures::task::singlethread::Task::wake_by_ref::h703eb36081e02274@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[17077]:0x88ff7c
wasm_bindgen_futures::task::singlethread::Task::into_raw_waker::raw_wake::hbbecd2c13a9ad09e@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[29756]:0xa32dad
core::task::wake::Waker::wake::h71c7a8b8547b075c@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[35955]:0xac4217
<wasm_bindgen_futures::JsFuture as core::convert::From<js_sys::Promise>>::from::finish::hf7c4ec0b61823ba2@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[1851]:0x3c62cb
<wasm_bindgen_futures::JsFuture as core::convert::From<js_sys::Promise>>::from::{{closure}}::hf3c5927074d5ee9e@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[24481]:0x99f5b7
core::ops::function::FnOnce::call_once::h96a98a949b83cb42@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[29110]:0xa2249b
<T as wasm_bindgen::closure::WasmClosureFnOnce<A,R>>::into_fn_mut::{{closure}}::h7fb1bcde18e18f98@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[27823]:0x9ffe1d
<dyn core::ops::function::FnMut<(A,)>+Output = R as wasm_bindgen::closure::WasmClosure>::describe::invoke::h2f0f59d65273821e@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[13770]:0x7f5131
__wbg_adapter_39@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7.js:262:10
real@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7.js:228:20
promise callback*getImports/imports.wbg.__wbg_then_e5489f796341454b@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7.js:1036:37
js_sys::Promise::then2::h113b718f534dbb49@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[24489]:0x99f9b3
<wasm_bindgen_futures::JsFuture as core::convert::From<js_sys::Promise>>::from::h105bb954f33c929d@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[953]:0x2d3c93
<wasm_streams::readable::into_stream::IntoStream as futures_core::stream::Stream>::poll_next::h329b8be037f6b1ae@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[1702]:0x3a439f
<S as futures_core::stream::TryStream>::try_poll_next::h7b62ed404bf901dc@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[35120]:0xab1ba8
<futures_util::stream::try_stream::into_stream::IntoStream<St> as futures_core::stream::Stream>::poll_next::h976f5a3ca806a4f9@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[32169]:0xa6eaa5
<futures_util::stream::stream::map::Map<St,F> as futures_core::stream::Stream>::poll_next::h46a43091b7c7a405@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[4933]:0x58bbe7
<futures_util::stream::try_stream::MapOk<St,F> as futures_core::stream::Stream>::poll_next::hd434abd1af1afe64@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[44857]:0xb6e11f
<S as futures_core::stream::TryStream>::try_poll_next::h410d90023d31e85f@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[51473]:0xbd7c85
<futures_util::stream::try_stream::into_stream::IntoStream<St> as futures_core::stream::Stream>::poll_next::h0e6f2832070162d8@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[44863]:0xb6e2c6
<futures_util::stream::stream::map::Map<St,F> as futures_core::stream::Stream>::poll_next::hb35e1d3947c2a172@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[2937]:0x490d3d
<futures_util::stream::try_stream::MapErr<St,F> as futures_core::stream::Stream>::poll_next::h8386fa25ffd1aeec@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[44858]:0xb6e166
<grpc_web_client::ReadableStreamBody as http_body::Body>::poll_data::h9c6edc673314e9f7@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[17395]:0x89db6e
grpc_web_client::call::GrpcWebCall<B>::poll_decode::h7c82ef4bf1e7d936@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[355]:0x1b1423
<grpc_web_client::call::GrpcWebCall<B> as http_body::Body>::poll_data::hefb42092c16e1146@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[24313]:0x99a19a
<http_body::combinators::box_body::UnsyncBoxBody<D,E> as http_body::Body>::poll_data::hd651f2641babc626@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[17396]:0x89dc20
<http_body::combinators::map_err::MapErr<B,F> as http_body::Body>::poll_data::h5f9b306ff3408a6f@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[1072]:0x2fb0d9
<http_body::combinators::box_body::UnsyncBoxBody<D,E> as http_body::Body>::poll_data::hd651f2641babc626@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[17396]:0x89dc20
<http_body::combinators::map_data::MapData<B,F> as http_body::Body>::poll_data::h02d2911fd590e16d@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[1036]:0x2ef8e4
<http_body::combinators::map_err::MapErr<B,F> as http_body::Body>::poll_data::hc86f8a7ebabd9e02@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[1079]:0x2fd3c5
<http_body::combinators::box_body::UnsyncBoxBody<D,E> as http_body::Body>::poll_data::hd651f2641babc626@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[17396]:0x89dc20
<tonic::codec::decode::Streaming<T> as futures_core::stream::Stream>::poll_next::h494aab25fcfcab73@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[161]:0x9862f
tonic::codec::decode::Streaming<T>::message::{{closure}}::{{closure}}::h99030203f38610cd@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[29886]:0xa363f5
<futures_util::future::poll_fn::PollFn<F> as core::future::future::Future>::poll::h7e101364509fecd4@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[30368]:0xa42874
tonic::codec::decode::Streaming<T>::message::{{closure}}::hfa2e81613ba8f8de@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[1189]:0x31e62d
<core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll::hb0af276daea41d84@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[9334]:0x6f249e
tonic::codec::decode::Streaming<T>::trailers::{{closure}}::hdf46cc28a4f917f0@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[270]:0x15aac3
<core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll::hf4a1ea9c1e949f45@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[8091]:0x69af5c
tonic::client::grpc::Grpc<T>::client_streaming::{{closure}}::hf124e996e6b9186c@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[206]:0xfce56
<core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll::h845fee34c15c1efc@http://127.0.0.1:4455/ui/webui-3c9c4549eb51aeb7_bg.wasm:wasm-function[8061]:0x698b9a

Looks like this is a regression after refactoring root_state.rs for 0.3.0.

Observer API

This issue aims to implement an API that is notified when a Slice or Atom is mutated.

One of the primary purpose for this API at the moment is to help with data persistence (e.g.: to local storage) that can be restored in its Default implementation at next page.

In a real application, I find almost never to be the case that one wants to persist the entire data store across visits and it requires all states to implement Serialize and Deserialize which I don't think it's realistic so this issue aims to design an API that can provide persistence and restoration on a state level.

Error using use two consecutive `use_query_value::<T>`

Hi! First, thanks for this amazing crate.

I'm trying to use two consecutive use_query_value::<T>, but I'm getting an error.

My source is something like this:

#[hook]
pub fn use_user_logged() {
    log::info!("01");
    let user_query = use_query_value::<LoggedUserQuery>(0.into());
    log::info!("02");

    let email = match user_query.result() {
        Some(Ok(m)) => m.logged_user.email.to_string(),
        _ => {
            log::info!("02 exit");
            return;
        }
    };

    let user_condition = TableField::new("user.email").equal(email);
    let user_request = DomainRequest::new("user", user_condition);

    log::info!("03");
    let use_user_domain = use_query_value::<DomainQuery>(user_request.into()); // <========exception occurs here
    log::info!("04");

    //...
}
I added some console infos to get the steps. The console log and the stack are here.

index-32d32242a8d24f2b.js:728 INFO src/states/user_listener.rs:56 01
index-32d32242a8d24f2b.js:728 INFO src/states/user_listener.rs:58 02
index-32d32242a8d24f2b.js:728 INFO src/states/user_listener.rs:63 02 exit
index-32d32242a8d24f2b.js:728 INFO src/services/api_request_gloo.rs:33 ### url: api/user...
index-32d32242a8d24f2b.js:728 INFO src/states/user_listener.rs:56 01
index-32d32242a8d24f2b.js:728 INFO src/states/user_listener.rs:58 02
index-32d32242a8d24f2b.js:728 INFO src/states/user_listener.rs:63 02 exit
index-32d32242a8d24f2b.js:728 INFO src/services/api_request_gloo.rs:68 ### url response: Ok(Response { url: "http://127.0.0.1:3000/api/user", redirected: false, status: 200, headers: Headers { access-control-allow-credentials: "true", content-length: "101", content-type: "application/json", date: "Mon, 03 Oct 2022 20:19:10 GMT", vary: "origin, access-control-request-method, access-control-request-headers" }, body_used: false })
index-32d32242a8d24f2b.js:728 INFO src/services/api_request_gloo.rs:72 ### url status: 200
index-32d32242a8d24f2b.js:731 DEBUG src/services/api_request_gloo.rs:78 Response: LoggedUser { id: "114130981533683136057", email: "[email protected]", name: None, given_name: None }
index-32d32242a8d24f2b.js:728 INFO src/states/user_listener.rs:56 01
index-32d32242a8d24f2b.js:728 INFO src/states/user_listener.rs:58 02
index-32d32242a8d24f2b.js:728 INFO src/states/user_listener.rs:71 03
index-32d32242a8d24f2b.js:920 Uncaught (in promise) Error: `unwrap_throw` failed
    at imports.wbg.__wbindgen_throw (index-32d32242a8d24f2b.js:920:15)
    at wasm_bindgen::throw_str::h53cebbd13a7eccef (index-32d32242a8d24f2b_bg.wasm:0x741387)
    at <core::result::Result<T,E> as wasm_bindgen::UnwrapThrowExt<T>>::expect_throw::h245aab6c05240048 (index-32d32242a8d24f2b_bg.wasm:0x55ddee)
    at wasm_bindgen::UnwrapThrowExt::unwrap_throw::hdc8d1c0854a1dff4 (index-32d32242a8d24f2b_bg.wasm:0x5f7876)
    at yew::functional::HookContext::next_state::h361dc7938ce62f05 (index-32d32242a8d24f2b_bg.wasm:0x316158)
    at <yew::functional::hooks::use_ref::UseMutRef<F> as yew::functional::hooks::Hook>::run::h3b1cf5a44df995da (index-32d32242a8d24f2b_bg.wasm:0x744968)
    at yew::functional::hooks::use_memo::use_memo_base::inner_fn::h0e4a63e86166658a (index-32d32242a8d24f2b_bg.wasm:0x331753)
    at <yew::functional::hooks::use_memo::use_memo_base::HookProvider<T,F,D,K> as yew::functional::hooks::Hook>::run::hf9db019d04b7d61c (index-32d32242a8d24f2b_bg.wasm:0x744b64)
    at yew::functional::hooks::use_memo::use_memo::inner_fn::h37766fe8f7a83e73 (index-32d32242a8d24f2b_bg.wasm:0x7415a7)
    at <yew::functional::hooks::use_memo::use_memo::HookProvider<T,F,D> as yew::functional::hooks::Hook>::run::hedac06a70c188af9 (index-32d32242a8d24f2b_bg.wasm:0x744a83)
imports.wbg.__wbindgen_throw @ index-32d32242a8d24f2b.js:920
$wasm_bindgen::throw_str::h53cebbd13a7eccef @ index-32d32242a8d24f2b_bg.wasm:0x741387
$<core::result::Result<T,E> as wasm_bindgen::UnwrapThrowExt<T>>::expect_throw::h245aab6c05240048 @ index-32d32242a8d24f2b_bg.wasm:0x55ddee
$wasm_bindgen::UnwrapThrowExt::unwrap_throw::hdc8d1c0854a1dff4 @ index-32d32242a8d24f2b_bg.wasm:0x5f7876
$yew::functional::HookContext::next_state::h361dc7938ce62f05 @ index-32d32242a8d24f2b_bg.wasm:0x316158
$<yew::functional::hooks::use_ref::UseMutRef<F> as yew::functional::hooks::Hook>::run::h3b1cf5a44df995da @ index-32d32242a8d24f2b_bg.wasm:0x744968
$yew::functional::hooks::use_memo::use_memo_base::inner_fn::h0e4a63e86166658a @ index-32d32242a8d24f2b_bg.wasm:0x331753
$<yew::functional::hooks::use_memo::use_memo_base::HookProvider<T,F,D,K> as yew::functional::hooks::Hook>::run::hf9db019d04b7d61c @ index-32d32242a8d24f2b_bg.wasm:0x744b64
$yew::functional::hooks::use_memo::use_memo::inner_fn::h37766fe8f7a83e73 @ index-32d32242a8d24f2b_bg.wasm:0x7415a7
$<yew::functional::hooks::use_memo::use_memo::HookProvider<T,F,D> as yew::functional::hooks::Hook>::run::hedac06a70c188af9 @ index-32d32242a8d24f2b_bg.wasm:0x744a83
$bounce::query::query_::use_query_value::inner_fn::h665425610704603b @ index-32d32242a8d24f2b_bg.wasm:0x1e2e7e
$<bounce::query::query_::use_query_value::HookProvider<T> as yew::functional::hooks::Hook>::run::hdd9ba11e18e3d997 @ index-32d32242a8d24f2b_bg.wasm:0x6faf7d
$roxi_example_frontend::states::user_listener::use_user_logged::inner_fn::hbc621f890677bf10 @ index-32d32242a8d24f2b_bg.wasm:0x570e9
$<roxi_example_frontend::states::user_listener::use_user_logged::HookProvider as yew::functional::hooks::Hook>::run::h9e08f3b9d98bd587 @ index-32d32242a8d24f2b_bg.wasm:0x77b1d7
$<roxi_example_frontend::components::app::Nav as yew::functional::FunctionProvider>::run::inner::hede1f4a9e0a4b005 @ index-32d32242a8d24f2b_bg.wasm:0xd279
...

The source of the error, in the yew framework, is this:

impl HookContext {
	\\...
    pub(crate) fn next_state<T>(&mut self, initializer: impl FnOnce(ReRender) -> T) -> Rc<T>
    where
        T: 'static,
    {
        // Determine which hook position we're at and increment for the next hook
        let hook_pos = self.counter;
        self.counter += 1;

        let state = match self.states.get(hook_pos).cloned() {
            Some(m) => m,
            None => {
                let initial_state = Rc::new(initializer(self.re_render.clone()));
                self.states.push(initial_state.clone());

                initial_state
            }
        };

        state.downcast().unwrap_throw() // <========exception occurs here
    }
	\\...
}

I'm using the main branches of yew and bounce-rs (git versions), with SSR.

Probably I'm doing something wrong, but I don't know what.
Thanks!

Helmet support for meta tags incorrectly merges OpenGraph meta tags

The Open Graph protocol (https://ogp.me) specifies the use of <meta> tags to provide graph data (it's how Facebook does fancy page metadata for links).

The distinguishing attribute for these is property but deduplication of meta tags in the helmet feature does not use that attribute when keying the tags during merge.

Please could you add property to the set of attributes used for meta tag deduplication?

Add tests

This issue aims to establish tests for bounce.

Query API

This issue aims to implement a Query API that can be used by a use_query_value or a use_mutation_value hook for fetching with http APIs.

pub enum QueryStatus {
    Idle,  // paused for queries, not started for mutations
    Loading,
    Ok,
    Err,
}

pub struct UseQueryValueHandle<T> {
    // ...
}

impl<T> UseQueryValueHandle<T> {
    fn status(&self) -> QueryStatus;
    fn result(&self) -> Option<Result<Rc<T::Output>, Rc<E>>>;
}

// use_query is reserved for suspension-based hooks.
let query: UseQueryValueHandle <T> = use_query_value::<T>(QueryBuilder::with_input(i).paused(false).build());
pub struct UseMutationValueHandle<T> {
    // ...
}

impl<T> UseMutationValueHandle<T> {
    fn status(&self) -> QueryStatus;
    fn result(&self) -> Option<Result<Rc<T::Output>, Rc<E>>>;
    fn start(&self, input: T::Input) -> T::Output;
}

// use_mutation is reserved for suspension-based hooks.
let mutation: UseMutationValueHandle<T> = use_mutation_value::<T>();

Merge yew-side-effect into Bounce

Needs: #13 (for server-side-rendering)

This issue aims to merge yew-side-effect into Bounce.

yew-side-effect consists of 2 main features:

  1. side effects (values of the same state are sorted in the order they are rendered)
  2. title provider (last rendered title will be set as document title)

Derived States

This issue aims to implement derived states (tentatively named Selector & Notion).

A Selector is a read-only state that derives from other states.

A Notion is a set-only state that manipulates other states.

pub struct StateReader { ... }

impl StateReader {
    pub fn get_slice_value<T: Slice>(&self);
    pub fn get_atom_value<T: Atom>(&self);
    pub fn get_selector_value<T: Selector>(&self);
}

pub struct StateSetter { ... }

impl StateSetter {
    pub fn get_slice_value <T: Slice>(&self);
    pub fn get_atom_value <T: Atom>(&self);
    pub fn get_selector_value <T: Selector>(&self);

    pub fn dispatch_slice_action<T: Slice>(&self, T::Action);
    pub fn set_atom_value<T: Atom>(&self, val: T);
    pub fn apply_notion<T: Notion>(&self, val: T);
}

pub trait Selector {
    fn get(states: StateReader) -> Rc<Self>;
}

pub trait Notion {
    fn apply(self, states: StateSetter);
}

let val: Rc<T> = use_selector_value::<T>();
let apply_t: Rc<dyn Fn(T)> = use_notion_applier::<T>();

Server-side Rendering support

This issue aims to implement server-side rendering support for the upcoming Yew 0.20 which supports server-side rendering.

  • BounceRoot needs a method to break the reference cycle.
  • Core API needs to make sure that they are not causing side effects in the rendering cycle / calling web APIs outside of effects.
  • Query API needs to implement a method to stream the response to the client side.
  • Helmet API needs to render in the effects and have a way to produce a string to be added to the <head /> element.

bounce wont compile

Cargo.toml

[dependencies]
bounce = { git = "https://github.com/bounce-rs/bounce", features = ["helmet"] }
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
log = "0.4.17"
console_log = { version = "0.2.0", features = ["color"] }
wasm-bindgen = "0.2.80"
yew-router = { git = "https://github.com/yewstack/yew" }
gloo = { version = "0.8.0", features = ["futures"] }
web-sys= "0.3.57"

Error:

error[E0004]: non-exhaustive patterns: `&VNode::VRaw(_)` not covered
   |
23 |     match tag {
   |           ^^^ pattern `&VNode::VRaw(_)` not covered
   |
note: `VNode` defined here
   |
16 | pub enum VNode {
   | --------------
...
34 |     VRaw(VRaw),
   |     ^^^^ not covered
   = note: the matched value is of type `&VNode`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
   |
38 ~         VNode::VSuspense(_) => throw_str("expected text content, found suspense."),
39 ~         &VNode::VRaw(_) => todo!(),
   |

error[E0004]: non-exhaustive patterns: `&VNode::VRaw(_)` not covered
   |
57 |     match node {
   |           ^^^^ pattern `&VNode::VRaw(_)` not covered
   |
note: `VNode` defined here
   |
16 | pub enum VNode {
   | --------------
...
   |

i dont know why, what or anything...
it doesnt work... idk

How do you use Query::Error?

I'm intrigued by bounce & am trying to make my own query hoping to propagate error along to my rendered components! But unfortunately, I do not see how I could do that without great toil: I'm using a reqwest client (to be precise, a client generated by progenitor), which returns errors that are neither PartialEq nor Clone, nor std::error::Error.

So I thought, hah, let's just use anyhow; it does all the reasonable error wrapping with minimal headache. Turns out, anyhow doesn't impl PartialEq either.

The best/easiest I could come up with is this, but I don't love it (sorry for the name, hah):

#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{}", .0)]
struct GoddamnIt(String);

impl GoddamnIt {
    fn new<E>(error: E) -> Self
    where
        E: ToString,
    {
        GoddamnIt(error.to_string())
    }
}

Do you have hints / recommendations on what to do that would help preserve the identifiability of my errors, or do you think the requirements for the Error associated type can be loosened?

Better Testing Support

This issue aims to implement APIs that makes writing tests easier:

  1. An API to seed bounce states before a test is run.
  2. An API to get bounce states after a component is rendered and proceeded to a certain state.
  3. An API to mock requests and responses (hard?)

Register Notions

Currently, when a notion is applied, it is dispatched to every state.

A notion is usually only applied a very limited number of states, iterate through every state and attempts to downcast to every kind of notion that is accepted by a state is not efficient.

This issue aims to update the notions API so that a reference of all states that accept a notion will be stored in a HashMap<TypeId, Vec<Rc<dyn AnyState>>> at the time a state is initialised.

Clarify the lifetime of values created in Bounc, especially InputSelector and Query

It would be helpful if the Book and API docs would make it clear how long state values created by Bounce live for.

I assume for basic Atoms, once you use them once they live for the full lifetime of the BounceRoot (is that true though?), which is why I want to focus on InputSelector and Query which are slightly less clear.

The documentation says that input selectors and queries are recalculated if the input changes, but the documentation for input selector also says "Each selector with a different input are treated as a different selector.", which sounds like InputSelector are keyed on values not just on types.

What happens when you change the input for an InputSelector or Query hook? Is the old value of the selector/query dropped? How does that work if I have two different components using the same InputSelector/Query but with different inputs?

My biggest concern is that I don't want to create a memory leak by repeatedly changing the input value for an InputSelector or Query and having the BounceRoot accumulate a massive cache of values with other old inputs just in case I use them again, though it would also be helpful to understand how InputSelector and Query work when there are different components sending different inputs, since I also wouldn't want to have the value repeatedly recalculated due to different components sending different inputs.

Examples:

#[derive(Properties, PartialEq)]
struct Props {
    user_id: Rc<u64>,
}

#[function_component]
fn GreetUser(props: &Props) -> Html {
    let user = use_query_value::<UserQuery>(props.user_id.clone());

    match user.result() {
        // The result is None if the query is currently loading.
        None => html! {<div>{"loading..."}</div>},
        // The result is Some(Ok(_)) if the query has loaded successfully.
        Some(Ok(m)) => html! {<div>{"User's name is "}{m.value.name.to_string()}</div>},
        // The result is Some(Err(_)) if an error is returned during fetching.
        Some(Err(e)) => html! {<div>{"Oops, something went wrong."}</div>},
    }
}

// Suppose I have an input that selects between different user_id values and pass them 
// down to the `GreetUser` component.
#[function_component]
fn UserSelection() -> Html {
    let selected_user = use_state(|| 0);
    // Every time this on-click fires, we will re-render `GreetUser` with a new input prop 
    // for `user_id`, which will then be passed to `use_query_value`. 
    //
    // What will happen to the old `User` value from the `UserQuery` after each
    // increment? Will it stay cached in the `BounceRoot` or will it get dropped?
    let onclick = {
        let selected_user = selected_user.clone();
        Callback::from(move |_| selected_user.set(*selected_user + 1))
    };

    html! {
        <div>
            <button {onclick}>{ "Next User" }</button>
            <GreetUser user_id={Rc::new(*selected_user)} />
        </div>
    }
}

// Suppose I try to run the same query with multiple different inputs at the same time:
html! {
    <div>
        // I assume this will "just work" and each greeter will run its query independently,
        // but it would be good if the documentation was more explicit about how inputs
        // to `InputSelector` and `Query` interact with their caching.
        <GreetUser user_id={Rc::new(0)} />
        <GreetUser user_id={Rc::new(1)} />
    </div>
}

`use_query` Doesn't Seem To Resume After Being Re-Rendered With New Input

I think this may be similar to #78 but I'm not entirely sure... sorry in advance if it is.

I have a simple reproducible scenario where I call a component that is designed to be frequently re-rendered with new props. Those props get fed into a query which goes off and does it's thing. On initial render, the component works great. If I completely unmount the component (via some intermediate state or something), and then render it again with new props, it also works great. But if I try to directly transition it from one state to another, it gets stuck in suspension.

I thought this might originally be an issue following the rules of hooks, but even this simple test scenario seems to break (I've also included a simple use of stylist here to highlight a typical component setup I'd be using, if it gives any more context, but just a plain use of use_query seems to break). A stripped-down example component looks like this:

use bounce::query::use_query;
use stylist::yew::{styled_component, use_style};
use uuid::Uuid;
use yew::prelude::*;

#[derive(Debug, PartialEq)
struct Response {
  value: String
}

#[derive(Properties, PartialEq)]
pub struct Props {
  pub id: Uuid
}

#[styled_component(Item)]
pub fn item(props: &Props) -> HtmlResult {
  let style = use_style!(r#"
    display: block;
  "#);

  let data = use_query::<Response>(props.id.clone().into())?;
  let value: AttrValue = data.value.clone().into();

  Ok(
    let data = data.as_ref().ok().unwrap();
    html! { <div class={style}>{value}</div> }
  )
}

For whatever it's worth:

  • use_query_value works perfectly with a component that is otherwise exactly the same (e.g. just changing HtmlResult to Html and doing the bare minimum changes to call sites impacted inside).
  • The hook seems to be invoked, but the query function itself doesn't seem to be invoked.

Thanks in advance for this amazing (set) of libraries -- if there is anything I can do to help patch this or look deeper, happy to lend a hand! Actually, if I find I'm using this appropriately (or am taught how to!), I'd be more than happy to write up some documentation for the book ๐Ÿ‘๐Ÿพ

Can not work with #[hook] for custom defined hooks.

The latest version of yew use the macro #[hook] to define a custom, but it does not work well bounce[version 0.3.0].
With only using hooks defined in yew is ok:

#[hook]
pub fn use_state_ref<T: Clone + PartialEq + 'static>(
    value: T,
) -> (yew::UseStateHandle<T>, Rc<RefCell<T>>) {
    let state = yew::use_state(|| value.clone());
    let state_ref = {
        let value = value.clone();
        yew::use_mut_ref(move || value)
    };
    {
        let state_ref = state_ref.clone();
        let state = state.clone();
        let state_dep = state.clone();
        yew::use_effect_with_deps(
            move |_| {
                *state_ref.borrow_mut() = (*state).clone();
                || ()
            },
            state_dep,
        );
    }

    (state.clone(), state_ref.clone())
}

It failed when using bounce:

#[hook]
pub fn use_bounce_state_ref<T: Clone + Atom + 'static>(
) -> (bounce::UseAtomHandle<T>, Rc<RefCell<T>>) {
    let state = bounce::use_atom::<T>();
    let state_ref = {
        let state = state.clone();
        yew::use_mut_ref(move || (*state).clone())
    };
    {
        let state = state.clone();
        let state_ref = state_ref.clone();
        yew::use_effect_with_deps(
            move |state_| {
                *state_ref.borrow_mut() = state_.clone();
                || ()
            },
            (*state).clone(),
        );
    }
    (state, state_ref)
}

This is the error message:
image

Refresh Queries is wrongly marked as suspend

Discovered by Rek Malorm on Discord.

Refresh Queries should not suspend the component and instead keeping the last fresh value until a new value is available.
However, sometimes it is possible for the use_query to suspend the component on a refresh query.
This can lead to unintentional fallback screen after data is loaded and is not desirable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.