useWatchEffect

Auto-tracked reactive side effects that run immediately and clean up automatically

Why Choose useWatchEffect?

Perfect when you want reactive side effects but don't want to manually specify dependencies. Think of it as a "smart useEffect" that knows which stores it depends on.

API Reference

Syntax

useWatchEffect(effect);

Parameters

  • effect: Function containing side effect logic (can return cleanup function)

Returns

  • undefined: No return value

Examples

Simple Reactive Side Effects

const userStore = createStore({
  name: "John",
  isOnline: false,
});

function DocumentTitleUpdater() {
  useWatchEffect(() => {
    // Automatically tracks userStore access
    document.title = `${userStore.value.name} - ${
      userStore.value.isOnline ? "Online" : "Offline"
    }`;
  });

  return <div>Title updater active</div>;
}

No dependency array needed - ZenBox automatically detects that this effect depends on userStore.

Auto-Save with Cleanup

const documentStore = createStore({
  content: "",
  autoSave: true,
  saveInterval: 30000,
});

function AutoSaveEffect() {
  useWatchEffect(() => {
    // Conditional logic with early return
    if (!documentStore.value.autoSave) return;

    console.log(
      `Setting up auto-save every ${documentStore.value.saveInterval}ms`
    );

    const interval = setInterval(() => {
      console.log("Auto-saving content...");
      api.saveDocument(documentStore.value.content);
    }, documentStore.value.saveInterval);

    // Cleanup runs automatically before next effect or on unmount
    return () => {
      console.log("Cleaning up auto-save interval");
      clearInterval(interval);
    };
  });

  return <div>Auto-save manager active</div>;
}

Smart dependency tracking: The effect automatically re-runs when autoSave, saveInterval, or content changes.

Analytics Tracker

const analyticsStore = createStore({
  userId: null,
  sessionId: null,
  pageViews: 0,
  events: [],
});

function AnalyticsTracker() {
  useWatchEffect(() => {
    const { userId, sessionId, pageViews } = analyticsStore.value;

    if (!userId || !sessionId) return;

    // Track page view count
    if (pageViews > 0) {
      analytics.track("page_view_count", {
        userId,
        sessionId,
        count: pageViews,
        timestamp: Date.now(),
      });
    }

    // Set up user identification
    analytics.identify(userId, {
      sessionId,
      lastActive: Date.now(),
    });

    console.log(
      `Analytics: Tracking user ${userId}, session ${sessionId}, ${pageViews} views`
    );
  });

  return null;
}

useWatchEffect vs useWatch

FeatureuseWatchEffectuseWatch
Dependency tracking✅ Automatic❌ Manual
Initial execution✅ Immediate❌ On changes by default
Configuration✅ Zero setup⚠️ Requires source param
Best forGeneral side effectsSpecific data watching

useWatchEffect - When you want "reactive useEffect":

// Automatic dependency tracking
useWatchEffect(() => {
  if (userStore.value.isLoggedIn) {
    initializeUserSession(userStore.value.id);
  }
});

useWatch - When you need precise control:

// Manual dependency specification
useWatch(
  () => userStore.value.loginAttempts,
  (attempts) => {
    if (attempts > 3) {
      lockAccount();
    }
  }
);

Best Practices

Keep effects focused and pure

// Good: Single responsibility
useWatchEffect(() => {
  document.title = `${appStore.value.title} - MyApp`;
});

Use early returns for conditional logic

// Good: Clear conditional logic
useWatchEffect(() => {
  if (!settingsStore.value.enableNotifications) return;

  setupNotificationService();
  return () => teardownNotificationService();
});

Always return cleanup functions for resources

// Good: Proper resource management
useWatchEffect(() => {
  const timer = setInterval(updateClock, 1000);
  return () => clearInterval(timer);
});

Don't modify reactive state inside effects

// Bad: Creates infinite loop
useWatchEffect(() => {
  const count = counterStore.value.count;
  counterStore.setState((state) => {
    state.count = count + 1; // This triggers the effect again!
  });
});

Don't use for component rendering state

// Bad: Use useStoreValue instead
const [localState, setLocalState] = useState("");
useWatchEffect(() => {
  setLocalState(store.value.data); // Unnecessary indirection
});

Hook Comparison

HookDependency TrackingImmediateRe-rendersUse Case
useWatchEffect✅ Automatic✅ Yes❌ NoGeneral reactive side effects
useWatch❌ Manual❌ No❌ NoSpecific data watching
useEffect❌ Manual deps✅ Yes❌ NoReact-specific effects
useStoreValue❌ Manual✅ Yes✅ YesComponent state synchronization
  • useWatch - Manual dependency watching with callbacks
  • useStoreValue - Component state synchronization with re-renders
  • useComputed - Derived reactive values for rendering