sveltejs / kit

web development, streamlined
https://kit.svelte.dev
MIT License
18.56k stars 1.91k forks source link

Parent Layout Data is not sent to page when invalidated by `await parent()` #9355

Open ottomated opened 1 year ago

ottomated commented 1 year ago

Describe the bug

When a +layout.server.ts load function is re-ran on the server side via await parent(), the data it returns is not sent down to the page.

This should either be considered a bug since the function is called and the data is already calculated, but it is not sent to the page, or there should be some way to manually invalidate it on the server-side (invalidate is client-only).

Note that invalidate does produce the expected result when called from the client.

Reproduction

https://stackblitz.com/edit/sveltejs-kit-template-default-xhrebt?file=src/routes/+layout.server.ts

Logs

No response

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 16.14.2 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 7.17.0 - /usr/local/bin/npm
  npmPackages:
    @sveltejs/adapter-auto: ^2.0.0 => 2.0.0 
    @sveltejs/kit: ^1.5.0 => 1.10.0 
    svelte: ^3.54.0 => 3.55.1 
    vite: ^4.0.0 => 4.1.4

Severity

serious, but I can work around it

Additional Information

No response

dummdidumm commented 1 year ago

This works as expected. The root layout does not have any dependencies on things like the URL which would make it rerun on a route change, the only dependency is the specific depends which you trigger through invalidate(..).

What's your use case? And what is your desired behavior? That the page load function can manually trigger a reload, or that the layout load runs on every page change? In the given example, you would want to rerun this on every page change, so you could opt into URL tracking by doing

export function ({ url }) {
  // ..
  url.pathname; // opt into rerunning load whenever the url changes
}
ottomated commented 1 year ago

@dummdidumm

In the given example, you would want to rerun this on every page change

I don't want to rerun it on every page change, I want it to rerun when I tell it to. I want the behavior of invalidate(...), but called from the +page.server.ts.

My use case is for trpc-svelte-query's SSR. In the root layout, I return a Map of SSR'd query keys to their values. The Map is stored in event.locals and written to by child pages. Currently, on a data request, there is no way to tell sveltekit to return the Map so I have to ignore SSR in those cases, which means I lose prefetching on hover, for example.

dummdidumm commented 1 year ago

Could you give a code example of what you expect this to look like?

ottomated commented 1 year ago

Could you give a code example of what you expect this to look like?

Simplified example:

+layout.server.ts

export const load = ({ locals, depends }) =>.{
  locals.map = new Map();
  depends('ssr');
  return { ssrData: locals.map };
};

+page.server.ts

export const load = ({ locals, invalidate }) =>.{
  locals.map.set('key', await fetch(/* ... */));
  invalidate('ssr');
};
dummdidumm commented 1 year ago

I don't understand this example. Doing this would just override the locals.map after you have set it.

This also uncovers a strong weirdness with that API: calling invalidate inside a load function would mean upper load functions run again, but what do that mean for the load function that calls it? Would it also rerun, or would it not rerun? If it would rerun, how would you know when not to call invalidate so you don't end up in an infinite loop? If it would not rerun, what would this mean for the input parameters of the load function? They can't magically update.

ottomated commented 1 year ago

In my example, I assume that the parent function is always called first. In my actual code, both contain if (!locals.map) locals.map = new Map(); – and it works fine, besides the fact that the return value of the parent load is not transferred to the page.

You might be overthinking the invalidate thing – it doesn't need to trigger any reruns in this case, it just has to behave identically to parent except it also serializes the return value of its parent onto the page. invalidate is probably the wrong word for it – in my mind, intuitively, parent should already trigger this behavior if it's running the parent function anyways.

timothycohen commented 9 months ago

Here's an example without invalidate, depends, or overwriting the key

I find it confusing that rerunning a load function doesn't guarantee the returned data is passed to page.data

mostrecent commented 9 months ago

FWIW, url.pathname in +layout.server.ts is super important and glad that I got hinted to this thread and we have this feature, why (but we could have more):

While you check authorization and enforce in hooks.server.ts and +page.server.ts, you want to let the views to know about the current status of the authorization, so it can show or hide things depending of the authorization state. Still everything needs to be approved by hooks.server.ts on request, so it is secure even if the data coming from +layout.server.ts would be spoofed or invalid or wrong.

So url.pathname is an absolute lifesaver, and you even don't need to await parent(). Otherwise I had to pass the user object in every load function.

My proposed solution now would be:

Great that we have url.pathname but +layout.server.ts runs now on every load, it would great if it actually ran only when the user object in its response changed. @dummdidumm is this technically possible?

I opened another issue here https://github.com/sveltejs/kit/issues/11415 which I close now, since we have this issue already.

mostrecent commented 9 months ago

@dummdidumm I wrote a follow-up in the other thread https://github.com/sveltejs/kit/issues/6315#issuecomment-1867281420 and will reopen https://github.com/sveltejs/kit/issues/11415

It would be great to have some way to auto-trigger a layout.server refresh on load func calls.

Issam-Jendoubi commented 8 months ago

We are experiencing the same behaviour. The parent layout load function is invalidated and therefore rerun. But its data is not sent to the child load function where it has await parent(). To be more concise, the invalidate triggers the rerun of the parent layout data and we can see that the load functions order is correct: parent layout -> child page with the updated parentData. However, navigating away from a child page to another child page, we witnessed that on the navigated to child page, the parent data is not up-to-date even though the parent layout load was invalidated and rerun as described above. The same behaviour is also happening when navigating back to the previous child page, which did trigger this child load function where we have also await parent(). But the parent data is not up-to-date. The assumption is that the layout parent data is somehow cached (without the cache being updated) or it is not sent as mentioned in this thread to the child pages. The url.pathname did solve this issue but it is not performant as it reruns with every page change under this layout of course. It is important to tell that this behaviour is witnessed starting fromSvelteKit version 2. The same mechanism of having parent layout load with child pages depending on their parent data worked fine with the up-to-date parent data whenever invalidated.

yuliankarapetkov commented 8 months ago

I have a similar issue where after using invalidate('some_key'), the data prop gets updated unlike $page.data:

// In child component:
invalidate('some_key');

// In parent
export let data;

$: console.log($page.data); // logs old value;
$: console.log(data); // logs new value; 
Lootwig commented 3 months ago

@yuliankarapetkov not a fix, but note that "some_key" does not match the required pattern described in the invalidate() docs:

If the argument is given as a string or URL, it must resolve to the same URL that was passed to fetch or depends (including query parameters). To create a custom identifier, use a string beginning with [a-z]+: (e.g. custom:state) — this is a valid URL.