createAsync not updating UI on client

const serverBoards = createAsync(async () => {
const boards = await getBoards(appContext.path);
const x = await Promise.all(
boards.map(async (board) => {
board.title = await decryptWithUserKeys(board.title);
return board;
})
);
console.log(x);
return x;
});
const serverBoards = createAsync(async () => {
const boards = await getBoards(appContext.path);
const x = await Promise.all(
boards.map(async (board) => {
board.title = await decryptWithUserKeys(board.title);
return board;
})
);
console.log(x);
return x;
});
the decryptWithUserKeys function is set so that it will return the input if running on server but on client it decrypts the data, the console.log confirms that the function is running and the data is decrypted but the UI never get's update on first load aka ssr load
20 Replies
Madaxen86
Madaxen86•4mo ago
Can you provide a minimal reproduction in a stack blitz?
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
Raqueebuddin Aziz
StackBlitz
solid-start-create-async-ssr-issue-repro - StackBlitz
Run official live example code for Solid-start With Tailwindcss, created by Solidjs on StackBlitz
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
note the issue is only on ssr load, if you save a file and hmr triggers, then it'll show right values
Madaxen86
Madaxen86•4mo ago
It looks fine to me. SSR returns the [0,1,2] array, console.log is [ '0', '1', '2' ] true That's what I would expect during SSR.
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
yes but look at browser console, it shows 20,21,22 but the UI doesn't the actual UI should be 20,21,22 since thats what decrypted is in client
Madaxen86
Madaxen86•4mo ago
Okay. Checked again and I see what you mean. But I've also only now realized that you are calling an async function directly in createAsync which is not correct. You should provide a regular function that executes the async function like this:
const getData = async () => {
const x = await NUMS().then((nums) => nums.map(decryptNum).map(String));
console.log(x, isServer);
return x;
};

export default function Home() {
const nums = createAsync(() => getData());
//onMount(() => revalidate(getNums.key));
return (
<main class="text-center mx-auto text-gray-700 p-4">
{nums()?.join(', ')}
</main>
);
}
const getData = async () => {
const x = await NUMS().then((nums) => nums.map(decryptNum).map(String));
console.log(x, isServer);
return x;
};

export default function Home() {
const nums = createAsync(() => getData());
//onMount(() => revalidate(getNums.key));
return (
<main class="text-center mx-auto text-gray-700 p-4">
{nums()?.join(', ')}
</main>
);
}
If you do this, there will be only the console.log which returns the isServer = true which is correct for the initial SSR rendering. You can force a revalidate with onMount (see comment in code) to trigger a client side revalidation. But I guess there's just a architectual issue in this part of your app. getBoards should not return different data during SRR and client side rendering.
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
getboards return same data, its the data processing that's different, how would you do client side decryption with SSR support? I don't wanna use revalidate like your example since its not actually invalid the encrypted data remains same
Madaxen86
Madaxen86•4mo ago
You can't run client-side code during SSR. And your approach would potentially lead to hydration issues. One approach would be to make the Component which uses the data a clientOnly component and do the decryption in that component. So you keep SSR for anything you need it, but can opt-out for client-onyl stuff. So you will call decrypt data only client-side and you can simplify it's data. I've provided 2x alternatives which then show the "decrypted" values: https://stackblitz.com/edit/github-hwfkud-csok28?file=src%2Froutes%2Findex.tsx But can't you decrypt also on the server and have encryption through https and the SSL certificate?
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
decrypting on the server means sending user private keys to server which breaks the zero trust model, so in solid start there is no way to server side prefetch the encrypted data and decrypt it on client? or does the data still prefetch with client only also do I need to clientOnly where the data is consumed or the component where the createAsync call happens also appreciate all the help 🙂
Madaxen86
Madaxen86•4mo ago
You only need the clientOnly for client-only code - decrypt - in your example. So if you decouple fetching boards from decryption you can still preload getBoards in the route. This would be example 1 in stack blitz.
Madaxen86
Madaxen86•4mo ago
An just discovored that @lxsmnsyc 🤖 has created a npm package with nice helpers: https://github.com/lxsmnsyc/solid-use/blob/main/docs/client-only.md
GitHub
solid-use/docs/client-only.md at main · lxsmnsyc/solid-use
A collection of SolidJS utilities. Contribute to lxsmnsyc/solid-use development by creating an account on GitHub.
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
appreciate that, but what is the reason for the discrepancy b/w the UI and the console.log anyways? is it intended or a bug
Madaxen86
Madaxen86•4mo ago
Not sure. Probably a bug because you've passed the async function directly to createAsync but it actually you should pass an "Accessor" which calls the async function without await like in this example where getData gets called only on the server during SSR. And that's how it should work to avoid hydration issues.
const getData = async () => {
const x = await NUMS().then((nums) => nums.map(decryptNum).map(String));
console.log(x, isServer);
return x;
};

export default function Home() {
const nums = createAsync(() => getData());
//onMount(() => revalidate(getNums.key));
return (
<main class="text-center mx-auto text-gray-700 p-4">
{nums()?.join(', ')}
</main>
);
}
const getData = async () => {
const x = await NUMS().then((nums) => nums.map(decryptNum).map(String));
console.log(x, isServer);
return x;
};

export default function Home() {
const nums = createAsync(() => getData());
//onMount(() => revalidate(getNums.key));
return (
<main class="text-center mx-auto text-gray-700 p-4">
{nums()?.join(', ')}
</main>
);
}
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
I don't think that's the issue since all async functions return a promise and I need a lot of it to be inline because I want to track upstream dependencies like appContext.path in the original code if I understand correctly, createAsync is just supposed to be createMemo but with async function support and they track deps only before the first yield to the event loop I tried sync code with createMemo and it has same issue, maybe this is intentional behaviour, or a bug that got slipped not sure
Madaxen86
Madaxen86•4mo ago
you mean like this?
const getData = async (path:string) => {
const x = await NUMS(path).then((nums) => nums.map(decryptNum).map(String));
console.log(x, isServer);
return x;
};

export default function Home() {
const nums = createAsync(() => getData(appContext.path));

//...
}
const getData = async (path:string) => {
const x = await NUMS(path).then((nums) => nums.map(decryptNum).map(String));
console.log(x, isServer);
return x;
};

export default function Home() {
const nums = createAsync(() => getData(appContext.path));

//...
}
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
Yes The issue happens with memo as well so don't think its related to async specifically
Madaxen86
Madaxen86•4mo ago
Sure, createMemo runs on the server and client. If you use the decrypt function inside it you'll potentiall end up with an hydration issue because it evaluates to different values on server and client. You definetly want to avoid it.
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
I guess I can run decrypt onMount seems like the easiest way
Madaxen86
Madaxen86•4mo ago
You just have to separate client and server code by using client only stuff after hydration. onMount will work or use one the Wrappers of solid-use.
Raqueebuddin Aziz
Raqueebuddin AzizOP•4mo ago
Thanks appreciate all the help. I think I have the solution now
Want results from more Discord servers?
Add your server