[🐞] Serializing dirty watch warning
Which component is affected?
Qwik Runtime
Describe the bug
I am getting this error:
QWIK WARN Serializing dirty watch. Looks like an internal error
It sounds like an error that outside users aren't expected to run into, yet I am.
Reproduction
https://stackblitz.com/edit/simple-qwik-starter-36wo3r?file=src%2Froutes%2Findex.tsx,package.json
Steps to reproduce
See the log in the stackblitz. Also, I would expect "Has Children" to be true.
System Info
System:
OS: macOS 13.1
CPU: (12) x64 Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
Memory: 50.63 MB / 16.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 18.12.1 - ~/.nvm/versions/node/v18.12.1/bin/node
Yarn: 1.22.18 - /usr/local/bin/yarn
npm: 8.19.2 - ~/.nvm/versions/node/v18.12.1/bin/npm
Browsers:
Brave Browser: 97.1.34.80
Chrome: 109.0.5414.87
Chrome Canary: 111.0.5558.0
Safari: 16.2
Safari Technology Preview: 16.4
npmPackages:
@builder.io/qwik: ~0.16.2 => 0.16.2
@builder.io/qwik-city: ~0.1.0-beta9 => 0.1.0-beta9
vite: 3.1.1 => 3.1.1
Additional Information
I can also cause the warning with this code that updates state in a useTask that is tracking the same state:
export default component$(() => {
const state = useStore({ test: true });
useTask$(({ track }) => {
track(state);
state.test = !state.test;
});
return <div>test</div>;
});
It's not too surprising that this causes problems, but the error could still be better.
I think the Stackblitz is a valid case that should be supported though.
This may be the minimal reproduction code:
export default component$(() => {
const state = useSignal(false);
useTask$(({track}) => {
track(() => state.value);
state.value = true;
});
return (
<div>
{JSON.stringify(state.value)}
</div>
);
});
You can test it here https://stackblitz.com/edit/simple-qwik-starter-goous6?file=src/routes/index.tsx
This seems like a user error:
useTask$(({track}) => {
track(() => state.value);
state.value = true;
});
why would you want to track and change the same property? It could easily end up in a infinite loop
You're right, it's probably not valid code. I think the main issue is that the error is confusing.
I recently cope with it too. I ended with having a signal receiving changes (output), and several tracked signals pushing changes to the output.
If possibly not the most ergonomic way, it was quiet straight forward.
const output = useSignal();
const state = useSignal();
useTask(({trak}) = {
track(() => state.value);
output.value = state.value
});
The error didn't help me much since I knew I was going to the wrong direction.
Any suggestion for improving the error @literalpie ?
Maybe something like "you cannot change a value in a useTask function that is also tracking that value."
yeah! makes sense! the Serializing dirty watch is more like a end result, good to have this issue because it likely reveals a bigger issue. Question:
useTask$(({track}) => {
track(() => state.value);
state.value = true; // < exception? or warning>?
});
eslint could definitively triggers an error.
In the event devs use that way on purpose, they can easily deactivate it.
🤔
With the minimal example I gave, I don't know if there's any valid way for this code to work (that wouldn't result in an infinite loop) so I think an exception makes sense to me.
But looking again at the more complicated example in the StackBlitz, I think that case might be valid? At least, it's not as clear to me how the code would result in an infinite loop or other issues.
I just stumbled upon this console warning too.
The WARN was not helpful enough on it's own to understand the cause (origin) or how to resolve it.
I found this error also when updating a context. For example :
layout.tsx
const CounterContext = createContextId('COUNTER');
const useCounterProvider = () => {
const state = useStore({ count: 0, lastCount: 0 });
useTask$(({ track }) => {
track(() => state.count);
if (state.count > state.lastCount) console.log('New observer');
})
}
child.tsx
const state = useContext(CounterContext);
useTask$(() => {
state.lastCount = state.count;
state.count++;
return () => {
state.lastCount = state.count;
state.count--;
}
})
I think this happen because the HTML is streamed so we cannot update the code after it has been send. But maybe it's something else.
@GrandSchtroumpf yes that's correct. If you change the app so that the task is used in a component that renders later it should not show the warning.
@wmertens thanks, but I want to query something only if there is at least one observer and multicast the result if there are multiple (much like a shareReplay in rxjs), so I need to do it on render.
layout.tsx
const QueryContext = createContextId('QUERY');
const useQueryProvider = () => {
const state = useStore({
count: 0,
lastCount: 0,
value: null,
});
useTask$(async ({ track }) => {
track(() => state.count);
if (state.count > state.lastCount && state.lastCount === 0) {
state.value = await fetchData();
}
})
}
child.tsx
const state = useContext(QueryContext);
useTask$(() => {
state.lastCount = state.count;
state.count++;
return () => {
state.lastCount = state.count;
state.count--;
}
});
// use state.value
Let me know if you see a way to achieve it without warning.
Also, and more importantly, I'm using SSG so I don't need streaming in the first place. Isn't it any way to disable it ?
I would create a hook that exposes a method so you can do the call on demand. See #5087.
Your code has an out-of-order race condition BTW, if two components exit the lastCount depends on the order.
Anyway, since you're doing SSG only, it doesn't matter that the build complains, the build is fine. Just put a message in your build script. But know that if you ever switch to SSR, it won't work as expected.
Ah, I meant to link to https://github.com/BuilderIO/qwik/issues/5087#issuecomment-1707185010 to show how to add methods to a store
seems to be as designed