Skip to content

Reactivator API: watch()

Appurist (Paul) edited this page Mar 12, 2021 · 5 revisions

watch()

Parameters: (obj, func)

Returns: For convenience, this function also returns the func parameter. See Comments section below.

This function installs a watch, which notifies of changes to the obj parameter supplied. The obj argument must be a reactivator object, that is, an object which was returned by a previous call to either ref() or reactive().

func: the callback function

The following is the function signature for the callback itself:

Parameters: (old, val, prop, name, obj)

None of the parameters are needed if the desire is only to be informed that obj has changed. Typically, especially in cases where ref() was used, only old and val are useful. However, for reactive() objects, in some cases the other parameters may be useful, such as in the case of the detailed console info provided in Example 2 below. So here is a description of all of these parameters:

  • old is the value prior to the reactive change notification
  • val is the new value
  • prop is the name of the field being changed. For watches on ref() objects, the prop will always be "value", but for watches on reactive() objects, it will be the name of the object member field being changed.
  • name provides the relative field names, when dealing with changes to sub-fields under obj, for example the ".sub1" in obj.sub1.field1. It will appear with the leading '.' if present, along with any .sub.sub.subfields.
  • obj is the ref or reactive object being modified, itself. This will typically not be used, since the watcher tends to know what it is watching. However, where a single watch() is used for several data items, this may be useful.

Additional Comments:

  • NOTE: The parameters on this watch() function vary dramatically from Vue's watch and watchEffect functions.
  • This function returns the func parameter, as a convenience for the caller. This allows anonymous lambda expressions to be captured for later use in an unwatch() call. See the use of savedHandler in the example below. This is also possible with syntax such as watch(count, savedFunc = (old,val) => { but in that case the savedFunc method would need to be declared previously, without initialization, adding to the burden of this call. That said, both forms remain available for use.
  • It is also worth noting that the return value and savedFunc can be ignored entirely if a call to unwatch will not be required later.

Simple example usage:

import { ref, watch, unwatch } from 'reactivator'

let count = ref(42)

count.value++;  // this one gets missed, no watch handler yet

let savedHandler = watch(count, (old,val) => {
  console.log(`count has changed from ${old} to ${val}`)
})

count.value++;  // this one is recorded by the watch above
count.value++;  // this one is recorded by the watch above

unwatch(count, savedHandler);

count.value++;  // this one gets missed

Resulting output:

count has changed from 43 to 44
count has changed from 44 to 45

A more advanced example:

This example provides more accurate reporting of the changes, through the use of a more complex onStateChange watch handler function. Also, when it detects the change is an object, it passes a true to dumpValue in order to have the value dumped as JSON:

import { reactive, watch, unwatch, dumpValue } from 'reactivator'

let state = reactive({field1: 42, field2: 'hello'})

// Let's define a more complex watch handler for object and array changes
function onStateChange(old, val, prop, name, obj) {
  let label = name ? `test3${name}` : 'test3';
  if (Array.isArray(obj) && prop === 'length')
    console.log(`watch: ${label}.${prop} changed to ${dumpValue(val)}`);
  else
  if (Array.isArray(obj) && parseInt(prop))
    console.log(`watch: ${label}[${prop}] changed from ${dumpValue(old)} to ${dumpValue(val)}: ${dumpValue(obj)}`);
  else
  if (old !== undefined) 
    console.log(`watch: ${label}.${prop} changed from ${dumpValue(old)} to ${dumpValue(val)}`);
  else
  if (Array.isArray(val))
    console.log(`watch: ${label}.${prop} assigned a new array ${dumpValue(val)}`);
  else
  if (typeof val === 'object')
    console.log(`watch: ${label}.${prop} assigned a new object: ${dumpValue(val, true)}`);
  else
    console.log(`watch: ${label}.${prop} assigned value ${dumpValue(val)}`);
}

state.field1 = 99;  // no watch installed yet

// Let's define a more complex watch handler for object and array changes
watch(state, onStateChange)

state.field1 = 100;
state.field3 = 'completely new';
state.field2 = 'Goodbye';

state.emptyArray = [  ];

state.newArray = [ 'one', 'two' ];
state.newArray.push('three');

state.sub1 = { sub1a: 'value1a', sub1b: 'value1b' };
state.sub1.sub1b = 'new1b';

unwatch(state, onStateChange); // now uninstall the watch
state.field1 = 101;

Resulting output:

watch: state.field1 changed from 99 to 100
watch: state.field3 assigned value 'completely new'
watch: state.field2 changed from 'hello' to 'Goodbye'
watch: state.emptyArray assigned a new array [ ]
watch: state.newArray assigned a new array ['one','two']
watch: state.newArray.2 assigned value 'three'
watch: state.newArray.length changed from 3 to 3
watch: state.sub1 assigned a new object: {"sub1a":"value1a","sub1b":"value1b"}
watch: state.sub1.sub1b changed from 'value1b' to 'new1b'