Vue.js course ยท Module 3: Reactivity

Project: Reactive NOVA LAB Control Panel

4 min read
In this lesson2

Final project of the Power Reactor module! Combine all Vue.js reactivity techniques in a single monitoring system. You'll build a complete plasma reactor dashboard that tracks parameters in real time, generates alarms, and optimizes resource usage.

Picture the night shift in the control room: the core temperature is rising, the pressure is dropping, and the engineer on duty has only a panel that refreshes when somebody remembers to refresh it. In this project you build a panel that needs no reminders - every reading, alarm and history entry reaches the screen because of the way you arrange the data.

Module Summary

In this module you learned:

  • ref() - reactive primitive variables. Access via .value in scripts, automatic unwrap in template. Ideal for single values like temperature, status, counter.
  • reactive() - reactive objects and arrays. Direct property access without .value. Cannot replace the entire object (only properties). Don't destructure without toRefs!
  • Deep vs shallow reactivity - shallowRef tracks only the replacement of .value, and shallowReactive only first-level properties. triggerRef forces an update after an in-place change. markRaw completely disables reactivity. Useful for large API data and external libraries.
  • toRef / toRefs - safe destructuring of reactive objects while preserving reactivity. toRef(obj, 'key') for a single property, toRefs(obj) for all.
  • Debugging tools - isRef(), isReactive(), unref(), toRaw() for checking and unwrapping reactive values.
  • Practical patterns - panels with validation, filtering systems, shopping carts with computed getters, and readonly() for sharing state without write access.

Project Requirements

Build a Reactor Monitoring System that includes:

1. Reactor state with reactive()

Create a reactive object holding reactor data: temperature (Kelvin), pressure (kPa), energy level (%), status (ACTIVE/STANDBY/CRITICAL). Add buttons to modify values.

The starter keeps the temperature in degrees Celsius and uses the thresholds 400 and 500 - switch it to kelvins and to the thresholds from the requirements below. Let the buttons call functions that change the object's properties. Never replace the object itself, because the template would lose its connection to it.

2. Alarms with ref()

Use ref([]) to store a list of system alarms. When temperature exceeds 4000K or pressure drops below 50kPa, automatically add an alarm to the list. Display alerts with the ability to dismiss them.

Check the thresholds in the same functions that change the readings - the checkAlarms function in the starter already does that. Give every alarm its own identifier and use it as the :key instead of the index, because new alarms land at the top of the list. Before adding one, check with the some() method whether the same alarm is already on display, so that every click does not add a duplicate.

3. Safe destructuring with toRefs

Use toRefs to extract individual values from the reactor object. Display them in separate interface components/sections without losing reactivity.

A change made through such a ref, for example temperature.value += 100, also changes reactor.temperature - it is the same wire, connected at both ends.

4. Optimization with markRaw

Create an array of historical readings and mark it as markRaw to avoid burdening the reactivity system. Display the last 10 readings in a history panel.

Here lies a trap: Vue does not observe an array marked with markRaw at all, so push() alone will not refresh the history panel. Keep the array in a shallowRef and call triggerRef after adding a reading, exactly as in the lesson about shallow reactivity:

1const history = shallowRef(markRaw([]))
2
3function recordReading(reading) {
4  history.value.push(markRaw(reading))
5  triggerRef(history)
6}

Every reading stays a plain object without a Proxy, and the panel refreshes once per entry. You can show the last ten entries with history.slice(-10) in the template, where the ref is already unwrapped.

I recommend building the project in layers: first the data, then the actions, and the template last, checking every layer in Vue DevTools. In module 4, in the Computing Center, you will replace the manual threshold checks with computed properties and watchers.

Expand the starter below!

Remember: a control panel is only as trustworthy as its data - keep it in one place, change it only through actions, and choose your reactivity deliberately.

Code for this lesson: App.vue
1<script setup>
2import { ref, reactive, toRefs, markRaw } from 'vue'
3
4// Reactor Monitoring System - Module 3 final project
5const reactor = reactive({
6  name: 'Reactor Alpha',
7  temperature: 350,
8  pressure: 101,
9  energy: 85,
10  status: 'NOMINAL'
11})
12
13const { temperature, pressure, energy, status } = toRefs(reactor)
14
15const alarms = ref([])
16
17// Historical logs - they don't need reactivity
18const historicalLogs = markRaw([
19  { time: '08:00', event: 'System start' },
20  { time: '08:15', event: 'Reactor online' },
21  { time: '08:30', event: 'All systems nominal' }
22])
23
24function adjustTemperature(delta) {
25  reactor.temperature += delta
26  checkAlarms()
27}
28
29function adjustPressure(delta) {
30  reactor.pressure += delta
31  checkAlarms()
32}
33
34function checkAlarms() {
35  if (reactor.temperature > 500) {
36    alarms.value.unshift('CRITICAL: Temperature exceeded!')
37    reactor.status = 'CRITICAL'
38  } else if (reactor.temperature > 400) {
39    alarms.value.unshift('WARNING: High temperature')
40    reactor.status = 'WARNING'
41  } else {
42    reactor.status = 'NOMINAL'
43  }
44}
45
46// TODO: Add more reactor parameters
47// TODO: Add automatic alarms for pressure
48// TODO: Expand the logging system
49</script>
50
51<template>
52  <div class="reactor-monitor">
53    <h1>Reactor Monitoring System</h1>
54    <div class="status-bar" :class="status.toLowerCase()">
55      Status: {{ status }}
56    </div>
57
58    <div class="panels">
59      <div class="panel">
60        <h3>Temperature</h3>
61        <p class="value">{{ temperature }}ยฐC</p>
62        <button @click="adjustTemperature(-10)">-10</button>
63        <button @click="adjustTemperature(10)">+10</button>
64      </div>
65      <div class="panel">
66        <h3>Pressure</h3>
67        <p class="value">{{ pressure }} kPa</p>
68        <button @click="adjustPressure(-5)">-5</button>
69        <button @click="adjustPressure(5)">+5</button>
70      </div>
71      <div class="panel">
72        <h3>Energy</h3>
73        <p class="value">{{ energy }}%</p>
74      </div>
75    </div>
76
77    <div class="alarms" v-if="alarms.length">
78      <h3>Alarms</h3>
79      <p v-for="(alarm, i) in alarms" :key="i">{{ alarm }}</p>
80    </div>
81  </div>
82</template>
83
84<style scoped>
85.reactor-monitor { background: #0a0a2e; color: #e0e0ff; padding: 2rem; min-height: 100vh; font-family: monospace; }
86h1 { color: #ff6b35; text-align: center; }
87.status-bar { text-align: center; padding: 0.5rem; border-radius: 4px; font-weight: bold; margin: 1rem 0; }
88.status-bar.nominal { background: #1a4a1a; color: #00ff88; }
89.status-bar.warning { background: #4a4a1a; color: #ffaa00; }
90.status-bar.critical { background: #4a1a1a; color: #ff4444; }
91.panels { display: flex; gap: 1rem; flex-wrap: wrap; }
92.panel { background: #1a1a4e; padding: 1rem; border-radius: 8px; flex: 1; min-width: 150px; text-align: center; }
93.value { font-size: 2rem; color: #00d4ff; margin: 0.5rem 0; }
94button { background: #333; color: white; border: none; padding: 0.5rem 1rem; margin: 0.25rem; cursor: pointer; border-radius: 4px; }
95button:hover { background: #555; }
96.alarms { margin-top: 1rem; background: #2a1a1a; padding: 1rem; border-radius: 8px; }
97.alarms h3 { color: #ff4444; }
98.alarms p { color: #ffaa00; font-size: 0.9rem; }
99</style>

Spotted a mistake in this lesson?

Useful articles