[🐞] sharedMap not available in server$ function
Which component is affected?
Qwik City (routing)
Describe the bug
I have a layout middleware that loads the current session to sharedMap, like this:
export const onRequest: RequestHandler = async ({ sharedMap }) => {
sharedMap.set("session", session);
};
Then I have a server$ function in a nested route that tries to access shared map but it returns an empty object, the function is like this:
export const getOffers = server$(async function () {
const { sharedMap } = this;
console.log("SHARED MAP", sharedMap);
});
Reproduction
https://stackblitz.com/edit/qwik-starter-gta4fs?file=src/routes/index.tsx
Steps to reproduce
As server functions don't work in stackblitz, you'll have to download the code and execute it locally
System Info
System:
OS: Linux 6.2 Fedora Linux 38 (Workstation Edition)
CPU: (12) x64 Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
Memory: 18.60 GB / 31.19 GB
Container: Yes
Shell: 5.9 - /bin/zsh
Binaries:
Node: 18.16.0 - ~/.volta/tools/image/node/18.16.0/bin/node
npm: 9.5.1 - ~/.volta/tools/image/node/18.16.0/bin/npm
Browsers:
Chrome: 112.0.5615.165
Firefox: 112.0.1
npmPackages:
@builder.io/qwik: ^0.100.0 => 0.100.0
@builder.io/qwik-city: ^0.100.0 => 0.100.0
undici: 5.21.0 => 5.21.0
vite: 4.2.1 => 4.2.1
Additional Information
No response
Just in case I would try upgrading to 103 in case this fixes it: https://github.com/BuilderIO/qwik/pull/3813
Oh, looks like sharedMap is available, it's just that the onRequest function in layout.tsx just isn't called for server$ it seems like.
@cmbartschat I was able to reproduce the bug with 103
where are you setting the sharedMap value?
@manucorporat He's setting it in the onRequest in the layout and trying to call the server$ on the client from useVisibleTask$, don't think this would work anyway since any calls to server$ don't invoke any onRequest for layout middleware. (Also, sharedMap is undefined for example even when onRequest is present in the same context, like for example during SSR with useTask$ calling server$)
Is this something you think should stay as is or do you think server$ should also sit behind layout middlewares?
there is a way to make this work, create a plugin.ts in src/routes and add the onRequest there.
plugin.ts files are always called, before any request!
Hi @manucorporat, is the plugin.ts file considered part of the stable Api?
@manucorporat Nice, didn't know about that! Works half-way, errors with TypeError: Cannot read properties of undefined (reading 'sharedMap') in SSR context, like calling server$ inside a useTask$:
export const getSession = server$(async function() {
console.log("Calling from `server$`...");
return this.sharedMap.get("session") as string;
});
export default component$(() => {
const sessionKey = useSignal<string>();
useTask$(async () => {
// This will error during SSR as `sharedMap` is `undefined`.
sessionKey.value = await getSession();
});
return (
<>
<div>Session key: {sessionKey.value}</div>
<button onClick$={async () => sessionKey.value = await getSession()}>Get Key!</button>
</>
);
});
which version of qwik are you using?
@manucorporat
"devDependencies": {
"@builder.io/qwik": "0.103.0",
"@builder.io/qwik-city": "~0.103.0",
"@types/eslint": "8.37.0",
"@types/node": "^18.15.9",
"@typescript-eslint/eslint-plugin": "5.59.0",
"@typescript-eslint/parser": "5.59.0",
"eslint": "8.38.0",
"eslint-plugin-qwik": "0.103.0",
"prettier": "2.8.7",
"typescript": "5.0.4",
"undici": "5.21.2",
"vite": "4.2.2",
"vite-tsconfig-paths": "4.2.0"
}
I notice that plugin.ts only applies once at the top-level directly inside src/routes and cannot be nested so you can't differentiate or guard routes anyway with it. Useful for some things but I think back to my original question on whether server$ should sit behind middlewares or stay as is? Thoughts?
since any calls to
server$don't invoke anyonRequestfor layout middleware. (Also,sharedMapis undefined for example even whenonRequestis present in the same context, like for example during SSR withuseTask$callingserver$)Is this something you think should stay as is or do you think
server$should also sit behind layout middlewares?
Yeah! this is intrinsec of server$ they can be called from any route, only routeAction, and routeLoader can be protected with a route guard.
I figured as much, I think that makes sense too, just making sure and I think that is the heart of this issue. If server$ is not to be behind route guards with layout then it doesn't make sense to set sharedMap in onRequest somewhere else which will never get run and access it inside server$.
So this issue is not an issue, I think it is working as intended.
@jordanw66 but server$ functions offer a very simple API and semantics, routeAction$ could do but those semantics are weird for cases where you want to query some info. For example, I'm polling for newly generated data periodically
@juanpmarin Yes I understand, server$ has a simpler API and is more flexible, whereas ...Action$ is more concrete and designed around forms/actions. I don't think there is currently a pleasing and simple way to achieve the following:
- Populate some piece of data on the page during SSR.
- Call back to the server to re-fetch data based on some criteria. (periodic, etc)
- Have this be protected by a route guard. (layouts)
- Do this in a performant and precise manner. (without re-fetching all other loaders, etc.)
I feel like there should be a streamlined way to achieve this like with a single routeLoader$ for example. It is achievable using a combination of features but with the following issues:
-
...Loader$s only load once on their own, no way to programmatically refresh only a specific loader without re-running or re-loading data for all the others. -
...Action$s can return data but cause all...Loader$s to reload. -
...Action$s returning data cannot be called during SSR. (insideuseTask$for example) -
server$works but is not route guarded and appears not intended to be.
With that said, this issue may be relevant to the above problem: https://github.com/BuilderIO/qwik/issues/2984
Yeah i agree, i think we will be able to solve this in an upcoming version of qwik
Any progress or ideas on this @manucorporat?
this should be fixed now but if you need middleware before server$ then you have to use qwik plugin.ts with plugin@