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 useStoreValue 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 |
useStoreValue | Component state sync | ✅ Yes | ❌ No | UI state |
useComputed | Derived reactive values | ✅ Yes | ✅ Yes | Calculated data |
Related Hooks
useWatchEffect
- Auto-tracked side effectsuseStoreValue
- Component state synchronizationuseComputed
- Derived reactive values