or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdcomponent-utilities.mdcomputed.mddependency-injection.mdindex.mdlifecycle.mdreactive-state.mdtypes.mdwatchers.md

watchers.mddocs/

0

# Watchers and Effects

1

2

Advanced watching system for tracking reactive data changes with configurable timing, cleanup, and side effects. Watchers enable you to perform side effects in response to reactive state changes.

3

4

## Capabilities

5

6

### Watch Function

7

8

Watches reactive data sources and executes a callback when they change. Supports watching single sources, multiple sources, and deep watching.

9

10

```typescript { .api }

11

/**

12

* Watches a single reactive source

13

* @param source - Reactive source to watch

14

* @param callback - Callback executed when source changes

15

* @param options - Watch configuration options

16

* @returns Function to stop watching

17

*/

18

function watch<T>(

19

source: WatchSource<T>,

20

callback: WatchCallback<T>,

21

options?: WatchOptions

22

): WatchStopHandle;

23

24

/**

25

* Watches multiple reactive sources

26

* @param sources - Array of reactive sources to watch

27

* @param callback - Callback executed when any source changes

28

* @param options - Watch configuration options

29

* @returns Function to stop watching

30

*/

31

function watch<T extends readonly unknown[]>(

32

sources: readonly [...T],

33

callback: WatchCallback<MapSources<T>>,

34

options?: WatchOptions

35

): WatchStopHandle;

36

37

/**

38

* Watches a reactive object with immediate and deep options

39

* @param source - Reactive object to watch

40

* @param callback - Callback executed when object changes

41

* @param options - Watch configuration options

42

* @returns Function to stop watching

43

*/

44

function watch<T extends object>(

45

source: T,

46

callback: WatchCallback<T>,

47

options?: WatchOptions<true>

48

): WatchStopHandle;

49

50

type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T);

51

type WatchCallback<T> = (value: T, oldValue: T, onInvalidate: InvalidateCbRegistrator) => void;

52

type WatchStopHandle = () => void;

53

type InvalidateCbRegistrator = (fn: () => void) => void;

54

```

55

56

**Usage Examples:**

57

58

```typescript

59

import { ref, reactive, computed, watch } from "@vue/composition-api";

60

61

// Watch a single ref

62

const count = ref(0);

63

const stopWatching = watch(count, (newValue, oldValue) => {

64

console.log(`Count changed from ${oldValue} to ${newValue}`);

65

});

66

67

// Watch multiple sources

68

const name = ref("Alice");

69

const age = ref(25);

70

watch([name, age], ([newName, newAge], [oldName, oldAge]) => {

71

console.log(`User changed from ${oldName}(${oldAge}) to ${newName}(${newAge})`);

72

});

73

74

// Watch computed property

75

const doubleCount = computed(() => count.value * 2);

76

watch(doubleCount, (newValue) => {

77

console.log(`Double count is now: ${newValue}`);

78

});

79

80

// Watch reactive object (deep by default)

81

const user = reactive({ name: "Bob", profile: { age: 30 } });

82

watch(user, (newUser, oldUser) => {

83

console.log("User object changed:", newUser);

84

});

85

86

// Stop watching when needed

87

stopWatching();

88

```

89

90

### Watch Options

91

92

Configuration options for customizing watch behavior including timing, depth, and immediate execution.

93

94

```typescript { .api }

95

interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {

96

immediate?: Immediate;

97

deep?: boolean;

98

}

99

100

interface WatchOptionsBase {

101

flush?: FlushMode;

102

}

103

104

type FlushMode = "pre" | "post" | "sync";

105

```

106

107

**Watch with Options:**

108

109

```typescript

110

import { ref, watch } from "@vue/composition-api";

111

112

const data = ref({ count: 0, nested: { value: 1 } });

113

114

// Immediate execution

115

watch(

116

data,

117

(newValue, oldValue) => {

118

console.log("Data changed:", newValue);

119

},

120

{ immediate: true } // Callback runs immediately with current value

121

);

122

123

// Deep watching (default for objects)

124

watch(

125

data,

126

(newValue) => {

127

console.log("Deep change detected");

128

},

129

{ deep: true }

130

);

131

132

// Flush timing control

133

watch(

134

data,

135

(newValue) => {

136

// Runs after DOM updates

137

console.log("DOM has been updated");

138

},

139

{ flush: "post" }

140

);

141

142

watch(

143

data,

144

(newValue) => {

145

// Runs before DOM updates (default)

146

console.log("Before DOM update");

147

},

148

{ flush: "pre" }

149

);

150

151

watch(

152

data,

153

(newValue) => {

154

// Runs synchronously

155

console.log("Sync execution");

156

},

157

{ flush: "sync" }

158

);

159

```

160

161

### Watch Effects

162

163

Runs an effect function immediately and re-runs it whenever its reactive dependencies change. Unlike watch, watchEffect doesn't need explicit sources.

164

165

```typescript { .api }

166

/**

167

* Runs effect immediately and re-runs when dependencies change

168

* @param effect - Effect function to run

169

* @returns Function to stop the effect

170

*/

171

function watchEffect(effect: WatchEffect): WatchStopHandle;

172

173

/**

174

* Post-flush watchEffect (runs after DOM updates)

175

* @param effect - Effect function to run

176

* @returns Function to stop the effect

177

*/

178

function watchPostEffect(effect: WatchEffect): WatchStopHandle;

179

180

/**

181

* Sync watchEffect (runs synchronously)

182

* @param effect - Effect function to run

183

* @returns Function to stop the effect

184

*/

185

function watchSyncEffect(effect: WatchEffect): WatchStopHandle;

186

187

type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void;

188

```

189

190

**Usage Examples:**

191

192

```typescript

193

import { ref, reactive, watchEffect, watchPostEffect } from "@vue/composition-api";

194

195

const count = ref(0);

196

const user = reactive({ name: "Alice", age: 25 });

197

198

// Basic watchEffect - automatically tracks dependencies

199

const stop = watchEffect(() => {

200

console.log(`Count is ${count.value}, user is ${user.name}`);

201

// This effect will re-run when count or user.name changes

202

});

203

204

// Effect with cleanup

205

watchEffect(async (onInvalidate) => {

206

const controller = new AbortController();

207

208

// Register cleanup function

209

onInvalidate(() => {

210

controller.abort();

211

});

212

213

try {

214

const response = await fetch(`/api/user/${user.name}`, {

215

signal: controller.signal,

216

});

217

const userData = await response.json();

218

console.log("User data:", userData);

219

} catch (error) {

220

if (error.name !== "AbortError") {

221

console.error("Failed to fetch user data:", error);

222

}

223

}

224

});

225

226

// Post-flush effect (runs after DOM updates)

227

watchPostEffect(() => {

228

// Access DOM elements after updates

229

const element = document.getElementById("counter");

230

if (element) {

231

element.textContent = count.value.toString();

232

}

233

});

234

235

// Stop the effect when needed

236

stop();

237

```

238

239

### Advanced Watching Patterns

240

241

Complex watching scenarios including conditional watching, async effects, and performance optimization.

242

243

**Conditional Watching:**

244

245

```typescript

246

import { ref, computed, watch } from "@vue/composition-api";

247

248

const isEnabled = ref(false);

249

const data = ref("initial");

250

251

// Only watch when condition is met

252

const conditionalStop = watch(

253

data,

254

(newValue) => {

255

if (isEnabled.value) {

256

console.log("Data changed:", newValue);

257

}

258

}

259

);

260

261

// Alternative: Dynamic watcher creation

262

let stopWatcher: (() => void) | null = null;

263

264

watch(isEnabled, (enabled) => {

265

if (enabled && !stopWatcher) {

266

stopWatcher = watch(data, (newValue) => {

267

console.log("Conditional watch:", newValue);

268

});

269

} else if (!enabled && stopWatcher) {

270

stopWatcher();

271

stopWatcher = null;

272

}

273

});

274

```

275

276

**Debounced Watching:**

277

278

```typescript

279

import { ref, watch } from "@vue/composition-api";

280

281

const searchTerm = ref("");

282

283

// Debounced search

284

watch(

285

searchTerm,

286

(newTerm, oldTerm, onInvalidate) => {

287

const timeout = setTimeout(() => {

288

console.log("Searching for:", newTerm);

289

// Perform search API call

290

}, 300);

291

292

// Cleanup previous timeout

293

onInvalidate(() => {

294

clearTimeout(timeout);

295

});

296

}

297

);

298

```

299

300

**Watching Getters with Dependencies:**

301

302

```typescript

303

import { ref, watch } from "@vue/composition-api";

304

305

const user = ref({ id: 1, name: "Alice" });

306

const posts = ref<any[]>([]);

307

308

// Watch a computed getter

309

watch(

310

() => user.value.id,

311

async (userId, prevUserId, onInvalidate) => {

312

const controller = new AbortController();

313

onInvalidate(() => controller.abort());

314

315

try {

316

const response = await fetch(`/api/users/${userId}/posts`, {

317

signal: controller.signal,

318

});

319

posts.value = await response.json();

320

} catch (error) {

321

if (error.name !== "AbortError") {

322

console.error("Failed to fetch posts:", error);

323

}

324

}

325

},

326

{ immediate: true }

327

);

328

```

329

330

### Watch in Component Setup

331

332

Typical usage patterns within Vue component setup functions with automatic cleanup.

333

334

```typescript

335

import { defineComponent, ref, watch, onUnmounted } from "@vue/composition-api";

336

337

export default defineComponent({

338

setup() {

339

const query = ref("");

340

const results = ref([]);

341

const loading = ref(false);

342

343

// Watchers are automatically cleaned up when component unmounts

344

watch(

345

query,

346

async (newQuery, oldQuery, onInvalidate) => {

347

if (!newQuery.trim()) {

348

results.value = [];

349

return;

350

}

351

352

loading.value = true;

353

const controller = new AbortController();

354

onInvalidate(() => {

355

controller.abort();

356

loading.value = false;

357

});

358

359

try {

360

const response = await fetch(`/api/search?q=${encodeURIComponent(newQuery)}`, {

361

signal: controller.signal,

362

});

363

const data = await response.json();

364

results.value = data.results;

365

} catch (error) {

366

if (error.name !== "AbortError") {

367

console.error("Search failed:", error);

368

results.value = [];

369

}

370

} finally {

371

loading.value = false;

372

}

373

},

374

{ immediate: false }

375

);

376

377

return {

378

query,

379

results,

380

loading,

381

};

382

},

383

});

384

```

385

386

## Types

387

388

```typescript { .api }

389

type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T);

390

391

type WatchCallback<V = any, OV = any> = (

392

value: V,

393

oldValue: OV,

394

onInvalidate: InvalidateCbRegistrator

395

) => any;

396

397

type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void;

398

399

type InvalidateCbRegistrator = (fn: () => void) => void;

400

401

type WatchStopHandle = () => void;

402

403

type MapSources<T, Immediate> = {

404

[K in keyof T]: T[K] extends WatchSource<infer V>

405

? Immediate extends true

406

? V | undefined

407

: V

408

: never;

409

};

410

411

type MultiWatchSources = (WatchSource<unknown> | object)[];

412

413

interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {

414

immediate?: Immediate;

415

deep?: boolean;

416

}

417

418

interface WatchOptionsBase {

419

flush?: FlushMode;

420

}

421

422

type FlushMode = "pre" | "post" | "sync";

423

424

interface VueWatcher {

425

lazy: boolean;

426

get(): any;

427

teardown(): void;

428

update(): void;

429

run(): void;

430

evaluate(): void;

431

depend(): void;

432

}

433

```