behaviour of createAsync in hooks - why cached?

I'm trying to get a better understanding on createAsync & query, and weather or not it is an appropriate way to share state. I created a hook and use it like this. This works perfectly fine. But I'm confused why the logs appear in the console, but no requests are made. I understood, that the query() cashes for only 5 seconds - so why is it not making requests all the timem even on navigation? I can import the hook and access the data from everywhere simultaneously. Honestly this is the behaviour i hoped for (its easy to revalidate the cash), but I'd like to fully understand why this works. Thanks!
//useMyHook.tsx
const getData = query(async () => {
"use server";
return { user: 1 };
}, "data");

export function useMyHook() {
const res = createAsync(() => getData());
return {
user: () => {
console.log("I'm am called and logged all the time!")
return res()?.user,
}
};
}
//useMyHook.tsx
const getData = query(async () => {
"use server";
return { user: 1 };
}, "data");

export function useMyHook() {
const res = createAsync(() => getData());
return {
user: () => {
console.log("I'm am called and logged all the time!")
return res()?.user,
}
};
}
// someComponent.tsx
export default function Page() {
const { user } = useMyHook();
return <div>{user()}</div>
}
// someComponent.tsx
export default function Page() {
const { user } = useMyHook();
return <div>{user()}</div>
}
❤️
9 Replies
peerreynders
peerreynders4w ago
You are showing useMyHook but the component is using useAuth. What is going on in between? As it stands the behaviour you are describing could be easily explained if you are using the same instance of createAsync across navigations.
rtzrtz.
rtzrtz.OP4w ago
Oh, thats a leftover from my actual code. I just edited it, thanks! Well, I'm using the same instance of the query, but not of the hook, right? I'm using the hook at multiple places, and each hook is a new instance, including the internal createAsync primitive.
Brendonovich
Brendonovich4w ago
query caches for 10-15 seconds, that may explain it Each createAsync is pulling from the same query, so they’ll only cause a refetch if they’re called 10-15 seconds after the first call
rtzrtz.
rtzrtz.OP4w ago
Mh, i just checked, it does not refetch the user, even after 15 seconds or longer
Brendonovich
Brendonovich4w ago
it won’t refetch automatically if that’s what you’re expecting
rtzrtz.
rtzrtz.OP4w ago
i hoped it wouldnt, but im still wondering why it doesnt. When I use const res = createAsync(() => getData()); on PageComponent (Home.tsx) it does refetch on every navigation, right? Why does it not refretch when used inside a hook?
Brendonovich
Brendonovich4w ago
Depends if the navigation causes a new instance of createAsync to be created. If not then no refetch will happen on navigate, since nothing will call getData
rtzrtz.
rtzrtz.OP4w ago
but every instance of the my hook, (see above, useMyHook) creates a new instance of createAsync as well, and its not triggereing a refetch
peerreynders
peerreynders4w ago
Something strange is going on here: Upon navigation: - First the reference count is incremented - then the clean up from the previous subscription runs This seems out of sequence. Because of this the reference count check always comes back with 1 so the staleness check is bypassed. SolidStart basic/TS template. Then
// file: src/routes/index.tsx
import { Title } from '@solidjs/meta';
import { createAsync } from '@solidjs/router';
import Counter from '~/components/Counter';
import { getData } from '../my-hook.js';

export default function Home() {
// const { user } = useMyHook();
const res = createAsync(() => getData());
return (
<main>
<Title>Hello World</Title>
<h1>Hello world!</h1>
<p>User: {res()?.user}</p>
<Counter />
<p>
Visit{' '}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{' '}
to learn how to build SolidStart apps.
</p>
</main>
);
}
// file: src/routes/index.tsx
import { Title } from '@solidjs/meta';
import { createAsync } from '@solidjs/router';
import Counter from '~/components/Counter';
import { getData } from '../my-hook.js';

export default function Home() {
// const { user } = useMyHook();
const res = createAsync(() => getData());
return (
<main>
<Title>Hello World</Title>
<h1>Hello world!</h1>
<p>User: {res()?.user}</p>
<Counter />
<p>
Visit{' '}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{' '}
to learn how to build SolidStart apps.
</p>
</main>
);
}
// file: src/routes/about.tsx
import { Title } from '@solidjs/meta';
import { createAsync } from '@solidjs/router';
import { getData } from '../my-hook.js';

export default function About() {
//const { user } = useMyHook();
const res = createAsync(() => getData());
return (
<main>
<Title>About</Title>
<h1>About</h1>
<p>User: {res()?.user}</p>
</main>
);
}
// file: src/routes/about.tsx
import { Title } from '@solidjs/meta';
import { createAsync } from '@solidjs/router';
import { getData } from '../my-hook.js';

export default function About() {
//const { user } = useMyHook();
const res = createAsync(() => getData());
return (
<main>
<Title>About</Title>
<h1>About</h1>
<p>User: {res()?.user}</p>
</main>
);
}
// file: src/my-hook.ts
import { createAsync, query } from '@solidjs/router';

const getData = query(async () => {
'use server';
console.log('getData', new Date());
return { user: 1 };
}, 'my-hook');

function useMyHook() {
const res = createAsync(() => getData());
console.log('useMyHook');
return {
user: () => {
console.log("I'm am called and logged all the time!");
return res()?.user;
},
};
}

export { getData, useMyHook };
// file: src/my-hook.ts
import { createAsync, query } from '@solidjs/router';

const getData = query(async () => {
'use server';
console.log('getData', new Date());
return { user: 1 };
}, 'my-hook');

function useMyHook() {
const res = createAsync(() => getData());
console.log('useMyHook');
return {
user: () => {
console.log("I'm am called and logged all the time!");
return res()?.user;
},
};
}

export { getData, useMyHook };
Debugging: http://localhost:3000/_build/node_modules/.pnpm/@[email protected][email protected]/node_modules/@solidjs/router/dist/data/query.js
but I'd like to fully understand why this works.
Basically the way it works right now is that as long as there is at least one createAsync (i.e. tracked) reference to the query the value is assumed to be "fresh". The whole "5 seconds" limit (PRELOAD_TIMEOUT) applies to the time that passes between a route preload:
import type { RouteDefinition } from '@solidjs/router';

export const route = {
preload: () => {
getData();
},
} satisfies RouteDefinition;
import type { RouteDefinition } from '@solidjs/router';

export const route = {
preload: () => {
getData();
},
} satisfies RouteDefinition;
(which presumably is not tracked) and the first tracked reference (createAsync); i.e. if for whatever reason it takes more than 5 secs between the preload and the first createAsync then the request is repeated. During navigation the reference of the previous route hasn't cleared yet, so when the next route registers its createAsync query doesn't refresh either. Aside: The idea behind your useMyHook function may not be optimal (you export useMyHook but not getData). In SolidStart the goal is to "fetch before you render" so a route would ideally warm up all the querys necessary for its components in its preload. That way components connect with their createAsyncs to querys that are already loading (or have already loaded). The aim is to hoist data loading all the way up to the route level rather than delaying it until component creation.

Did you find this page helpful?