useWatch
Execute side effects when reactive data changes without triggering component re-renders
API Reference
Syntax
const currentValue = useWatch(watchSource, callback, options?)Parameters
watchSource: Store instance, computed function, or array of sources to watchcallback: Function executed when watched data changes (can return cleanup function)options(optional):immediate: Execute callback immediately with current value (default: false)once: Execute callback only once, then stop watching (default: false)deep: Enable deep equality checking for nested objects (default: false)
Returns
currentValue: The current value of the watched source
Configuration Options
Immediate Execution
useWatch(
  () => userStore.value.preferences,
  (preferences) => {
    applyUserPreferences(preferences);
  },
  { immediate: true } // Applies preferences immediately on building the component
);One-time Watchers
useWatch(
  () => userStore.value.isLoggedIn,
  (isLoggedIn) => {
    if (isLoggedIn) {
      console.log("First login detected!");
      showWelcomeMessage();
    }
  },
  { once: true } // Only triggers once
);Deep Equality Checking
const configStore = createStore({
  api: {
    endpoints: { users: "/api/users", orders: "/api/orders" },
    timeout: 5000,
  },
});
useWatch(
  () => configStore.value.api,
  (apiConfig, prevApiConfig) => {
    console.log("API configuration changed");
    reinitializeApiClient(apiConfig);
  },
  { deep: true } // Deep comparison prevents unnecessary triggers
);Examples
Watch Store Changes
Track store changes for logging, analytics, or external API calls:
const counterStore = createStore({ count: 0 });
function Counter() {
  // Side effect only - no re-renders triggered
  useWatch(counterStore, (current, prev) => {
    console.log(`Count: ${prev.count} → ${current.count}`);
    // Track analytics
    analytics.track("counter_changed", {
      from: prev.count,
      to: current.count,
    });
  });
  return (
    <button
      onClick={() =>
        counterStore.setState((s) => {
          s.count++;
        })
      }
    >
      Increment
    </button>
  );
}Watch Computed Values
Monitor derived data for business logic:
const userStore = createStore({
  firstName: "John",
  lastName: "Doe",
  age: 30,
});
function UserTracker() {
  // Watch full name changes
  useWatch(
    () => `${userStore.value.firstName} ${userStore.value.lastName}`,
    (fullName, prevFullName) => {
      if (prevFullName) {
        console.log(`Name updated: "${prevFullName}" → "${fullName}"`);
        api.updateUserProfile({ fullName });
      }
    }
  );
  // Watch age milestones
  useWatch(
    () => userStore.value.age,
    (age, prevAge) => {
      if (age >= 18 && prevAge < 18) {
        console.log("User became an adult!");
        unlockAdultFeatures();
      }
    }
  );
  return <div>Tracking user changes...</div>;
}Watch Multiple Sources
Monitor changes across multiple stores simultaneously:
const userStore = createStore({ name: "John", role: "user" });
const settingsStore = createStore({ theme: "light", language: "en" });
function AppWatcher() {
  useWatch(
    () => [userStore.value, settingsStore.value] as const,
    ([user, settings], [prevUser, prevSettings]) => {
      // Role-based logic
      if (user.role !== prevUser?.role) {
        console.log(`Role changed: ${prevUser?.role} → ${user.role}`);
        redirectBasedOnRole(user.role);
      }
      // Theme persistence
      if (settings.theme !== prevSettings?.theme) {
        console.log(`Theme: ${prevSettings?.theme} → ${settings.theme}`);
        document.body.className = settings.theme;
        localStorage.setItem("theme", settings.theme);
      }
    }
  );
  return <div>Monitoring app state...</div>;
}Cleanup Functions
Perfect for managing subscriptions, timers, and external resources:
function WebSocketManager() {
  useWatch(
    () => userStore.value.connectionId,
    (connectionId, prevConnectionId) => {
      if (!connectionId) return;
      console.log(`Connecting to: ${connectionId}`);
      const ws = new WebSocket(`ws://api.com/${connectionId}`);
      ws.onopen = () => console.log("Connected");
      ws.onmessage = (event) => handleMessage(event.data);
      ws.onerror = (error) => console.error("WebSocket error:", error);
      // Cleanup function - automatically called on next change or unmount
      return () => {
        console.log(`Disconnecting from: ${connectionId}`);
        ws.close();
      };
    }
  );
  return <div>WebSocket manager active</div>;
}Best Practices
✅ Use for side effects only
// Good: External side effects
useWatch(
  () => store.value.theme,
  (theme) => {
    document.body.className = theme;
    localStorage.setItem("theme", theme);
  }
);❌ Don't use for component state
// Bad: Use useStore instead
const [displayValue, setDisplayValue] = useState("");
useWatch(
  () => store.value.data,
  (data) => {
    setDisplayValue(data); // This creates unnecessary indirection
  }
);✅ Be specific with watchers
// Good: Watch specific fields
useWatch(
  () => userStore.value.email,
  (email) => {
    validateEmail(email);
  }
);❌ Avoid watching large objects
// Bad: Triggers on any store change
useWatch(
  () => largeStore.value,
  (store) => {
    console.log("Something changed"); // Too broad
  }
);✅ Always clean up resources
// Good: Proper cleanup
useWatch(
  () => store.value.intervalMs,
  (ms) => {
    const interval = setInterval(doSomething, ms);
    return () => clearInterval(interval);
  }
);Hook Comparison
| Hook | Purpose | Re-renders | Auto-tracking | Best For | 
|---|---|---|---|---|
useWatch | Manual dependency watching | ❌ No | ❌ No | Specific side effects | 
useWatchEffect | Auto-tracked side effects | ❌ No | ✅ Yes | General side effects | 
useStore | Component state sync | ✅ Yes | ❌ No | UI state | 
useComputed | Derived reactive values | ✅ Yes | ✅ Yes | Calculated data | 
Related Hooks
useWatchEffect- Auto-tracked side effectsuseStore- Component state synchronizationuseComputed- Derived reactive values