qwik icon indicating copy to clipboard operation
qwik copied to clipboard

[🐞] Serializing dirty watch warning

Open literalpie opened this issue 3 years ago • 10 comments

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.

literalpie avatar Jan 25 '23 03:01 literalpie

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

genki avatar Feb 27 '23 17:02 genki

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

manucorporat avatar Mar 18 '23 15:03 manucorporat

You're right, it's probably not valid code. I think the main issue is that the error is confusing.

literalpie avatar Mar 18 '23 20:03 literalpie

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 ?

tleperou avatar Mar 18 '23 22:03 tleperou

Maybe something like "you cannot change a value in a useTask function that is also tracking that value."

literalpie avatar Mar 18 '23 22:03 literalpie

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>?
  });

manucorporat avatar Mar 19 '23 07:03 manucorporat

eslint could definitively triggers an error.

In the event devs use that way on purpose, they can easily deactivate it.

🤔

tleperou avatar Mar 19 '23 11:03 tleperou

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.

literalpie avatar Mar 19 '23 16:03 literalpie

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.

n8sabes avatar Apr 22 '23 14:04 n8sabes

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 avatar Oct 08 '23 09:10 GrandSchtroumpf

@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 avatar Oct 08 '23 10:10 wmertens

@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 ?

GrandSchtroumpf avatar Oct 08 '23 10:10 GrandSchtroumpf

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.

wmertens avatar Oct 08 '23 13:10 wmertens

Ah, I meant to link to https://github.com/BuilderIO/qwik/issues/5087#issuecomment-1707185010 to show how to add methods to a store

wmertens avatar Oct 08 '23 14:10 wmertens

seems to be as designed

wmertens avatar Jan 19 '24 17:01 wmertens