Reactive

reactive

Creates a reactive proxy object that tracks property access and changes for automatic reactivity.

Signature

function reactive<T extends object>(target: T): T;

Parameters

  • target: The object to make reactive. Can be a plain object, array, Map, or Set.

Returns

A reactive proxy of the original object that tracks all property access and mutations.

Basic Usage

import { reactive } from '@quantajs/core';

const state = reactive({
  count: 0,
  name: 'Quanta',
  user: {
    firstName: 'John',
    lastName: 'Doe',
  },
});

// All changes are tracked automatically
state.count++;
state.name = 'QuantaJS';
state.user.firstName = 'Jane';

Supported Types

Objects

const user = reactive({
  name: 'Alice',
  age: 25,
  preferences: {
    theme: 'dark',
    notifications: true,
  },
});

user.age = 26; // Tracked
user.preferences.theme = 'light'; // Nested changes tracked

Arrays

const todos = reactive([
  { id: 1, text: 'Learn QuantaJS', done: false },
  { id: 2, text: 'Build an app', done: false },
]);

// Array mutations are tracked
todos.push({ id: 3, text: 'Deploy', done: false });
todos[0].done = true; // Nested property changes tracked
todos.splice(1, 1); // Array method changes tracked

Maps

const userMap = reactive(new Map([
  ['user1', { name: 'Alice', role: 'admin' }],
  ['user2', { name: 'Bob', role: 'user' }],
]));

// Map operations are tracked
userMap.set('user3', { name: 'Charlie', role: 'user' });
userMap.get('user1').role = 'moderator'; // Nested changes tracked
userMap.delete('user2');
console.log(userMap.size); // Size changes tracked

Sets

const uniqueIds = reactive(new Set([1, 2, 3, 4, 5]));

// Set operations are tracked
uniqueIds.add(6);
uniqueIds.delete(1);
console.log(uniqueIds.size); // Size changes tracked

Deep Reactivity

Nested objects and arrays are automatically made reactive:

const store = reactive({
  users: [
    { id: 1, profile: { name: 'Alice', settings: { theme: 'dark' } } },
    { id: 2, profile: { name: 'Bob', settings: { theme: 'light' } } },
  ],
  metadata: {
    total: 2,
    lastUpdated: new Date(),
  },
});

// All nested changes are tracked
store.users[0].profile.settings.theme = 'light';
store.metadata.total = 3;
store.users.push({ id: 3, profile: { name: 'Charlie', settings: { theme: 'auto' } } });

Performance Considerations

Large Objects

For large objects, consider using specific selectors:

const largeStore = reactive({
  items: Array.from({ length: 10000 }, (_, i) => ({ id: i, value: Math.random() })),
  metadata: { /* large object */ },
});

// Good: Access specific properties
const itemCount = largeStore.items.length;
const firstItem = largeStore.items[0];

// Avoid: Iterating over entire large arrays in watchers
// watch(() => largeStore.items, (items) => { ... }); // Expensive!

Computed Values

Combine with computed for derived state:

import { reactive, computed } from '@quantajs/core';

const state = reactive({
  items: [1, 2, 3, 4, 5],
});

const sum = computed(() => state.items.reduce((a, b) => a + b, 0));
const average = computed(() => sum.value / state.items.length);

console.log(sum.value); // 15
console.log(average.value); // 3

state.items.push(6);
console.log(sum.value); // 21
console.log(average.value); // 3.5

Integration with Stores

When using createStore, the state is automatically made reactive:

import { createStore } from '@quantajs/core';

const userStore = createStore('user', {
  state: () => ({
    profile: {
      name: 'John Doe',
      email: 'john@example.com',
    },
    preferences: {
      theme: 'dark',
      notifications: true,
    },
  }),
  actions: {
    updateProfile(updates) {
      Object.assign(this.profile, updates); // All changes tracked
    },
    toggleTheme() {
      this.preferences.theme = this.preferences.theme === 'dark' ? 'light' : 'dark';
    },
  },
});

// All state changes trigger reactivity automatically
userStore.updateProfile({ name: 'Jane Doe' });
userStore.toggleTheme();

Limitations

Primitive Values

Reactive only works with objects. For primitives, use refs or wrap in objects:

// This won't work as expected
const count = reactive(0); // ❌

// Use an object instead
const state = reactive({ count: 0 }); // ✅
state.count = 5;

// Or use computed for derived primitives
const doubleCount = computed(() => state.count * 2);

Non-Enumerable Properties

Non-enumerable properties are not tracked:

const obj = reactive({});
Object.defineProperty(obj, 'hidden', { value: 'secret', enumerable: false });

// This won't trigger reactivity
obj.hidden = 'new secret';

Learn More