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

use_location doesnt recognize new query string on query string updates #3089

Closed
2 of 3 tasks
mara-schulke opened this issue Jan 16, 2023 · 2 comments
Closed
2 of 3 tasks
Labels

Comments

@mara-schulke
Copy link

mara-schulke commented Jan 16, 2023

Problem
I have written a small hook for my application which syncs a serializable state with the query parameters / search string of the page location.

This is required to persist state through page reloads / page shares.

The biggest problem i encountered is that the yew_router hooks rerender my component with an outdated querystring even tho the query string is mutated (for example through pressing a link button or using the browsers forward / backward buttons). Why does that not happen?
It looks like the navigation event is beeing picked up and the component rerenders, just search field of the location state is out of sync.

Also i encountered it to be pretty unergonomic to be forced to provide the route of the page i am on, just for mutating the query string through use_navigator, is there a better way to do this?

My hook / code looks like this:

use std::ops::Deref;

use yew::prelude::*;
use yew_hooks::{use_effect_update_with_deps, use_location};
use yew_router::{
    prelude::{use_navigator, use_route, Navigator},
    Routable,
};

#[derive(Debug, Clone)]
pub struct UseQueryStringStateHandle<R, S>
where
    R: Routable,
    S: serde::Serialize
        + for<'de> serde::Deserialize<'de>
        + Clone
        + Default
        + std::fmt::Debug
        + PartialEq
        + 'static,
{
    route: R,
    navigator: Navigator,
    inner: UseStateHandle<S>,
}

impl<R, S> UseQueryStringStateHandle<R, S>
where
    R: Routable + 'static,
    S: serde::Serialize
        + for<'de> serde::Deserialize<'de>
        + Clone
        + Default
        + std::fmt::Debug
        + PartialEq
        + 'static,
{
    pub fn set(&self, new: S) {
        self.navigator.push_with_query(&self.route, &new).ok();
        self.inner.set(new);
    }
}

impl<R, S> Deref for UseQueryStringStateHandle<R, S>
where
    R: Routable + 'static,
    S: serde::Serialize
        + for<'de> serde::Deserialize<'de>
        + Clone
        + Default
        + std::fmt::Debug
        + PartialEq
        + 'static,
{
    type Target = S;

    fn deref(&self) -> &Self::Target {
        self.inner.deref()
    }
}

#[hook]
pub fn use_query_string_state<R, S>() -> UseQueryStringStateHandle<R, S>
where
    R: Routable + 'static,
    S: serde::Serialize
        + for<'de> serde::Deserialize<'de>
        + Clone
        + Default
        + std::fmt::Debug
        + PartialEq
        + 'static,
{
    let route = use_route().unwrap();
    let navigator = use_navigator().unwrap();
    let location = use_location();

    let state = {
        let location = location.clone();
        use_state_eq(move || {
            serde_qs::from_str::<S>(&location.search.replace("?", ""))
                .unwrap_or_else(|_| S::default())
        })
    };

    {
        let state = state.clone();
        let location = location.clone();
        let search = location.search.clone();
        log::info!("rerender {:?}", search);

        use_effect_update_with_deps(
            move |_| {
                log::info!("recognized update");

                state.set(
                    serde_qs::from_str::<S>(&location.search).unwrap_or_else(|_| S::default()),
                );

                || {}
            },
            search,
        );
    }

    UseQueryStringStateHandle {
        route,
        navigator,
        inner: state,
    }
}

Steps To Reproduce

#[derive(Clone, Debug, Routable, PartialEq)]
enum Route {
    #[at("/")]
    Home
}

impl Route {
    fn resolve(&self) -> Html {
        html! { <Home /> }
    }
}

#[function_component]
fn App() -> Html {
    html! {
        <BrowserRouter>
             <Link<Route> to={Route::Home}>{"Reset Parameters"}</Link<Route>>
             <Switch<Route> render={Route::resolve} />
        </BrowserRouter>
    }
}

#[function_component]
fn Home() -> Html {
    #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
    struct State { value: u8 }

    let state = use_query_string_state::<Route, State>();

    let onclick = {
        let state = state.clone();
        Callback::from(move |_: MouseEvent| { state.set(State { value: state.value + 1 }) })
    };

    html! {
        <>
            {format!("Value {}", state.value)}
            <button type="button">
                {"Increment"}
            </button>
        </>
    }
}

Expected behavior
Provide up to date query string through location.search when using use_location to access the search parameters once they are changed.

Environment:

  • Yew version: 0.20
  • Rust version: 1.65
  • Browser and version, if relevant: Chromium 107

Questionnaire

  • I'm interested in fixing this myself but don't know where to start
  • I would like to fix and I have a solution
  • I don't have time to fix this right now, but maybe later
@mara-schulke mara-schulke changed the title use_location doesnt rerender on query string updates use_location doesnt recognize new query string on query string updates Jan 16, 2023
@futursolo
Copy link
Member

futursolo commented Jan 30, 2023

use yew_hooks::{use_effect_update_with_deps, use_location};

You should subscribe to location with yew_router::hooks::use_location() and not other methods.
We do not guarantee how the history is updated if it is subscribed otherwise as window.history is an asynchronous API.

@voidpumpkin
Copy link
Member

I am with @futursolo on this one.
So I'll close the issue, assume as Wont Fix status

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

No branches or pull requests

3 participants