daief
daief

Reputation: 23

Reactive declarations seems not work? Or am I using the 'hooks' the wrong way?

I am writing a SPA Svelte app with svelte-navigator. I want to fetch a request when pathname is changed.

import { useLocation } from "svelte-navigator";
import { useCallApi } from "./hook";

const loc = useLocation();
const [resp, fetchPageData] = useCallApi(); // my `hooks` to call api

$: pathname = $loc.pathname;
$: loading = $resp.loading;
$: data = $resp.data;

$: {
  console.log("loading changed", loading);
}

$: if (pathname) {
  console.log(pathname);
  fetchPageData(); // when pathname change, call a api
}

fetchPageData is called when pathname changed everytime, but the reactive declarations($: loading) does not update everytime.

My way to use hooks looks like this:

import { writable } from 'svelte/store';

export function useCallApi() {
    let result = writable({
        loading: false,
        data: 0,
    });

    const fakeFetch = async () => {
        result.update(pre => ({
            data: 0,
            loading: true,
        }));
        
        await new Promise(r => setTimeout(r, 500));
        
        result.update(pre => ({
            data: Math.random(),
            loading: false,
        }));
    };

    return [result, fakeFetch];
}

The online demo is https://svelte.dev/repl/e367c0aa3c9743cb8a749f92f0239d59?version=3.32.1 .

When click To a or To b to change the pathname, reactive declarations update only once.

But when click the button trigger request, all state update likes what I expected.

I want to know what is wrong with this, svelte, svelte-navigator or my hooks? And why?

I would be most grateful if you could help me out.


updated: 2021-02-02

My friend found that the statements order affects the update behavior, which looks somewhat like the link. If put fetchPageData() to the top, the update behavior looks good.

import { useLocation } from "svelte-navigator";
import { useCallApi } from "./hook";

const loc = useLocation();
const [resp, fetchPageData] = useCallApi();

//  the order is important which will affect the update behavior
$: if (pathname) {
  console.log(pathname);
  fetchPageData();
}

$: pathname = $loc.pathname;
$: loading = $resp.loading;
$: data = $resp.data;

$: {
  console.log("loading changed", loading, $resp.loading);
}

Upvotes: 1

Views: 321

Answers (1)

Simon Dehaut
Simon Dehaut

Reputation: 2679

I don't really know why but when you replace the auto-subscription $ by resp.subscribe() it works :

import { useLocation } from "svelte-navigator";
import { useCallApi } from "./hook";

const loc = useLocation();
const [resp, fetchPageData] = useCallApi(); // my `hooks` to call api

$: pathname = $loc.pathname;

let loading;
let data;
    
resp.subscribe( result => {
    loading = result.loading;
    data = result.data;
});

$: {
  console.log("loading changed", loading);
}

$: if (pathname) {
  console.log(pathname);
  fetchPageData(); // when pathname change, call a api
}

You could try it here : https://svelte.dev/repl/ca065fdc188e41938b8e3db0b8c61f20?version=3.32.1

I think the answer is behind the way svelte executes reactive statements.

According to : https://stackoverflow.com/a/63977760/12153710 :

svelte batches all the changes to update them in the next update cycle, and before it updates the DOM, it will execute the reactive declarations to update the reactive variables.

When you update your router from a , I think that compiled code waits the promise to resolve (in hook.js) before updating the reactive statement $: loading = $resp.loading; and $: data = $resp.data; and therefore the { loading } and { data } for the DOM.

Upvotes: 1

Related Questions