---
# System prepended metadata

title: Design `onWatch` v1

---

# Design `onWatch` v1

Design API which can be used to watch for changes in the inputs and then cause an effect.


## Constraints

- Qwik does not execute the inputs on each rendering, instead Qwik relies on subscriptions.
- Execution of the update function needs to be lazy loaded


## Proposal

```typescript=
/**
 * Provides ability to re-run a function when watched inputs change.
 * 
 * `onWatch` allows for execution of a function which has a side effect.
 * The function is re-executed every time one of the guarded inputs 
 * change. (See `WatchGuard`.) It is expected that the execution of
 * the `effectFn` has a side-effect.
 * 
 * The `efectFn` is stored as QRL and therefore is lazy loaded.
 * 
 * Use the `WatchGuard` to create subscriptions on data which are
 * considered as inputs to the `effectFn`. Whenever these properties 
 * change the `effectFn` is re-run.
 * 
 * @param effectFn: QRL pointing to a function which will re-execute.
 */
export function onWatch(watcher: QRL<(obs: Observer) => void>);
export function onWatch$(watcher: (obs: Observer) => void);


/**
 * Wraps an object in a read proxy which creates a subscription on 
 * that property.
 * 
 * Any subsequent changes to the object will trigger the `useEffect`
 * function to rerun.
 */
export type WatchGuard<T extends {}> = ();
```

```typescript=
import {component$, $} from '@builder.io/qwik';

const MyComponent = component$(() => {
  const store = useStore(); // assume id: STORE123
  onWatch$((obs) => {
    // This will create a subscription on `store.city` property
    // any time the property changes this function will re-run.
    const city = obs(store).city; 
    store.temp = await fetch(`http://weather/${city}`);
  }));
  
  return onRender(() => <span>{store.city} / {store.temp}</span>);
});
```

The above will encode like so:
```htmlembedded=
<div on:q-render="..."
     on:q-watch="chunk@effectSymbol|STORE123.city[*STORE123]">
</div>
```

## Open Questions
- When should the API run 
  - Before / after `on:qRender`?
    - In React `useEffect` runs after `render` so that `useEffect` can use `createRef`
  - Should we somehow coalesce events?
  - When a set of subscriptions change, how long before the `onWatch` runs?
    - currently `onRender` runs after `rAF`.
  - `onWatch` can return `Promise` which means it can delay other things?

*Debate*:
- `onWatch` should happen after rendering so that it can read layout information.
- do we want `useWatch` which runs before and after `onRender`?
- Should `onWatch` be synchronous? This makes sense. If you want to execute async operations and then cancel it.
- 

*Insights*:
- if `onWatch` runs after `onRender` this means that it also runs through `rAF`


NOTE:
- If it is important that UI updates are applied atomically then we have to render to JSX buffer first and then apply to UI

*Conclusions*:
- `onWatch` must:
  - return synchronously. 
  - Framework makes best effort to run in front of render, but not guaranteed. 
    - (`onWatch` function may need to be loaded in which case `rAF` of `onRender` will get in front of it.)
  - May return cancel fn `()=>void`, which will run before next invocation.
  - Runs on next microtask after detecting input change.
    - this implies before onRender because `onRender` runs `rAF`
    - We may need `onLayout()` (to run after `onRender` which allows reading DOM)


---

```typescript=
class {
  componentWillRender() {
    // Funcionality A
    // Funcionality B
  }
      
      
  componentDidRender() {
    // Funcionality A
    // Funcionality B
    // Funcionality C
  }
}


const useMyThing = () {
    onBeforeRender(()=>{
        // do some stuff
    }
}
```

```typescript=

function FetchTemp() {
  return {
    cancel() {
      ...
    },
    fetch(city: string, callback: Function) {
      ...
    }
  };
}

cost MyComp = component$(() => {
  const store = useStore({city: '', temp: 0});
  const ref = useRef();


  onWatch$((obs) => {
    const city = obs(store).city;
    const {res, cancel} = fetchWithCancel('http...city');

  });
  onWatch$((obs) => {
    if (obs(ref).current) {
      console.log('runs after render?')
    }
  })


  onDidRender$(()=> {
    console.log('read layout here.');
  });
  return onRender$(() => {
    return <span>{store.count}</span>
  });
});
```

---

# Manu

```typescript=
interface MyAppProps {
  city: string;
}

interface State {
  state: 'initial' | 'loading' | 'done';
  name?: string;
  weather?: string;
  temp?: string;
}

export const MyApp = qComponent((props: MyAppProps) => {

  const store = useStore<State>({state: 'initial'});

  onWatch$(async (obs) => {
    store.state = 'loading';
    const res = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${obs(props).city}&appid=0d590602bfaa37f01d8bec9322b4df72&units=metric`,
      {
        method: 'GET',
        headers: {},
      }
    );
    const json = await res.json();
    Object.assign(store, {
      state: 'done',
      ...json,
    });
  }));

  return onRender(() => {
      
    const ready = markReady(store.state === 'done');
      
    return (
      <div>
        {ready ? (
          <>
            <h1>City: {store.name}</h1>
            <h2>Weather: {store.weather}</h2>
            <h2>Temp: {store.temp}C</h2>
          </>
        ) : (
          <span>LOADING</span>
        )}
      </div>
    );
  });
});
```


```typescript=

export const MyApp = qComponent(async (props: MyAppProps) => {
  console.log('Fetching weather for', props.city);
  const res = await fetch(
    `https://api.openweathermap.org/data/2.5/weather?q=${props.city}&appid=0d590602bfaa37f01d8bec9322b4df72&units=metric`,
    {
      method: 'GET',
      headers: {},
    }
  );
  const json = await res.json();

  const state = {
    name: json.name,
    weather: json.weather[0].description,
    temp: json.main.temp,
  };

  return onRender(() => {
    return (
      <div>
        <h1>Prop: {props.city}</h1>
        <h1>City: {state.name}</h1>
        <h2>Weather: {state.weather}</h2>
        <h2>Temp: {state.temp}C</h2>
      </div>
    );
  });
});
```

# FAQ
- Why can't we just supply args to watch?

```typescript=
export const MyComp = component$(() => {
  const store = useStore({a: 0, b:0});
  const a = store.a;
  const x = onWatch$(() => a + store.b, [a, store.b]); // This is re-executed
  return onRender$(() => <span>{x}</span>)
})

```


- Why do we need to have `WatchGuard`?

```typescript=
import {$} from '@builder.io/qwik';

export const MyComp = component$(() => {
  const store = useStore({a: 0, b:0});
  const a = store.a;
  const x = useEffect$(() => a + store.b); // This is re-executed
  useMyStuff$(() => console.log(''))
  return onRender$(() => <button on$:click={() => console.log('click')}>{x}</button>)
})

function useMyStuff$(fn:QRL<() =>void>) {
  useEffectFromQrl(fn)
}

export const MyComp = componentFromQrl($(() => {
  const store = useStore({a: 0, b:0});
  const a = store.a;
  const x = useEffectFromQRl($(() => a + store.b)); // This is re-executed
  return onRenderFromQrl($(() => <button on:click={$(() => console.log('click'))}>{x}</button>))
}))


    
onWatch  -----> onRender
onRender --/--> onWatch

const a = onWatch(watcher => {
    return 12;
})
    
    
function useEffect$(fn) {
    
    onWatch(fn);
}

useWatch(w => {
    
}) 
    
useObserve(observer => {
    
})
useObserve(obs => {
    
})
    
```

```typescript=
export const MyComp = component$(() => {
  const store = useStore({a: 0, b:0});
  const a = store.a;
  onWatch$((_) => _(a) + _(store).b); // This is re-executed
  return onRender$(() => <span>{x}</span>)
})

```


```typescript=
export const MyComp = component$(({a}) => {
  const store = useStore({a: 0, b:0});
    
    
  //const a = store.a;
  const x = useEffect$((obs) => {
      const {c} = obs(store);
      return store.a + store.b + c;
  }); // This is re-executed
  return onRender$(() => <span>{x}</span>)
})

```

- How to handle async loading?

---

```typescript=
export default function useDebounce(obj, key, delay) {
  // State and setters for debounced value
  const [debouncedValue, setDebouncedValue] = useState(value);

  obj[key]
  useEffect(() => {
    // Set debouncedValue to value (passed in) after the specified delay
    const handler = setInterval(() => {
      setDebouncedValue(value);
    }, delay);
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}
```

1. Call: useEffect() + unreg()
2. Call: unreg(), useEffect() + unreg()


const [code, setCode] = useState();
const debounced = useDebounce(code, 500);

useEffect(()=>{
  // Compilation
  compile(debounced);
}, [debounced])

```htmlembedded=
<div q:base="/build">
  <button on:click="foo.js">/build/foo.js</button>
  <button on:click="/foo.js">/foo.js</button>
</div>
<div q:base="http://server/build">
  <button on:click="foo.js">/build/foo.js</button>
  <button on:click="/foo.js">http://server/foo.js</button>
</div>
```