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

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

Open
ottomated opened this issue Mar 7, 2023 · 15 comments · May be fixed by #13076
Open

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

ottomated opened this issue Mar 7, 2023 · 15 comments · May be fixed by #13076
Labels
bug Something isn't working
Milestone

Comments

@ottomated
Copy link
Contributor

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
Copy link
Member

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
Copy link
Contributor Author

ottomated commented Mar 8, 2023

@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
Copy link
Member

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

@ottomated
Copy link
Contributor Author

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
Copy link
Member

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
Copy link
Contributor Author

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
Copy link
Contributor

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
Copy link

mostrecent commented Dec 21, 2023

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 #11415 which I close now, since we have this issue already.

@mostrecent
Copy link

@dummdidumm I wrote a follow-up in the other thread #6315 (comment) and will reopen #11415

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

@Issam-Jendoubi
Copy link

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 from SvelteKit 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
Copy link

yuliankarapetkov commented Feb 12, 2024

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

This comment was marked as off-topic.

@eltigerchino
Copy link
Member

I've tested this on the latest version of SvelteKit and the parent load is re-run when a child load function depends on it through await parent() so the page does get the latest data from both load functions.

@timothycohen
Copy link
Contributor

This example with the latest release still has the same issue:

Expected:

Server logs:
/layout +layout.server.js returning {"returnedFromLoadButNotUpdating":"56:58"}

$page.data:
{"returnedFromLoadButNotUpdating":"56:58"}

Actual:

Server logs:
/layout +layout.server.js returning {"returnedFromLoadButNotUpdating":"56:58"}

$page.data:
{"returnedFromLoadButNotUpdating":"56:16"} <--

@eltigerchino eltigerchino reopened this Oct 30, 2024
@eltigerchino
Copy link
Member

eltigerchino commented Oct 30, 2024

Ok, so the issue is awaiting the parent, but not returning the parent data, results in a stale $page.data when the parent and child data are merged by SvelteKit. This can be observed by navigating through the pages in the reproduction below:
https://stackblitz.com/~/github.com/eltigerchino/sveltejs-kit-template-default-hbt4lp

@eltigerchino eltigerchino added the bug Something isn't working label Oct 30, 2024
@eltigerchino eltigerchino added this to the soon milestone Oct 30, 2024
@eltigerchino eltigerchino linked a pull request Nov 29, 2024 that will close this issue
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants