DEV Community

Sibasish Mohanty
Sibasish Mohanty

Posted on

Reactive Patterns in Vue: watch, two-way binding, and lifecycle hooks

In our Dear React, Meet Vue series, we’ve built a basic Tic-Tac-Toe with props/emits, reactive arrays, and computed state. Today, we’ll layer on:

  • Collecting player names and form validation
  • Focusing inputs as soon as the form appears
  • Watching game state changes for side-effects

You’ll see how Vue lets you treat state as plain JS, emit events instead of callbacks, and hook into reactivity without boilerplate hook arrays.

1. Player Setup Form: v-model for Two-Way Binding

Imagine asking teammates for their names before kickoff. In React, you’d juggle value, onChange, and manual setters. In Vue:

<input v-model="names.x" placeholder="Player X name" />
<span v-if="errors.x">Name required</span>
Enter fullscreen mode Exit fullscreen mode

v-model secretly wires up both :value="names.x" and @input="names.x = $event.target.value". No setNames calls—just assign to names.x as if it were a plain variable. Reactive errors.x flips to “Name required” the moment names.x is empty, thanks to a watchEffect rerunning our tiny validator.

2. Lifecycle Hook: Auto-Focus

You wouldn’t ask players to introduce themselves without a microphone—so let’s focus the first input. In React you’d write a useEffect with an empty array; in Vue:

onMounted(() => {
  formRef.value.querySelector('input').focus()
})
Enter fullscreen mode Exit fullscreen mode

onMounted fires exactly once, after the form renders—no “did I forget a dependency?” paranoia.

Template ref="formRef" gives you a direct line to the DOM, no extra hook arrays or refs juggling. Checkout the Vue docs for Lifecycle Diagram.

3. Reactive Side-Effects: watch vs. watchEffect

Once our players are named and the game starts, we want to log each move and celebrate the winner with a toast. In React you might sprinkle useEffect hooks everywhere; in Vue:

watch(squares, (newBoard, oldBoard) => {
  console.log('Move made:', newBoard)
})

watch(winner, (who) => {
  if (who) alert(`${who} wins!`)
})
Enter fullscreen mode Exit fullscreen mode

watch tracks specific sources (squares, winner) and only fires when they actually change. No dependency arrays required; Vue’s Proxy and computed graph handle it for you.

For quick-and-dirty reactions (like auto-validating names), watchEffect reruns whenever any dependency inside changes.

4. Move History: Rewind & Replay

As the React tutorial explores “time travel” in their Tic-Tac-Toe, we’ll do the same in Vue—only with way less boilerplate:

 // 1. History stores board snapshots
const history = reactive([Array(9).fill(null)])
const step = ref(0)

 // 2. Compute the current board from history
const squares = computed(() => history[step.value])

function handleSelect(i) {
  if (squares.value[i] || winner.value) return

  // 3. Clone and mutate the current snapshot
  const next = squares.value.slice()
  next[i] = xIsNext.value ? 'X' : 'O'

  // 4. If we rewound, toss out future snapshots
  history.splice(step.value + 1)

  // 5. Record the new state and advance
  history.push(next)
  step.value++
}
Enter fullscreen mode Exit fullscreen mode
  • Reactive history: Think of history as our DVR. Vue wraps it in a Proxy, so calls to history.splice() or history.push() magically update any computed or template bound to it, no more setHistory needed.
  • Step pointer: Our step is the remote’s playhead. Changing step.value rewinds or fast-forwards to that snapshot, and the board redraws itself.
  • Computed board: squares is just computed(() => history[step.value]). Vue auto-tracks dependencies, so you never have to re-initialize or call a setter, history drives everything.

In React land, you’d juggle useState for both history and step, splice with spreads, and call setHistory([...history.slice(0, step+1), next]) on every move. Vue’s approach? State is plain JavaScript. You mutate history in place, Vue’s reactivity patches only the diffs, and computed props flow downstream automatically. No spreads, no manual setters; just intuitive array ops that feel like second nature.

Wrapping Up & What’s Next

We’ve come a long way from our initial Vite setup—discovering how Vue’s template-first syntax, ref/reactive state, props/emits, computed, watch, v-model, and lifecycle hooks replace React’s hooks and boilerplate with a more intuitive, JS-native mental model. We built a fully reactive Tic-Tac-Toe, added a player-setup form with instant validation, and even rewound the game with move history—all without a single setState spread or dependency array to juggle.

Next up, we’ll take a step back and really think in Vue—mirroring React’s “Thinking in React” approach. We’ll break down how to decompose UIs into components the Vue way, organize data flow, and architect apps around Vue’s core mental model.

Stay tuned!

Top comments (0)