Skip to content

System Events

State containers emit system events for lifecycle management. Subscribe with onSystemEvent().

Available Events

EventPayloadWhen
stateChanged{ state, previousState }After state changes
propsUpdated{ props, previousProps }After props update
disposevoidWhen dispose() is called

Usage

typescript
class UserCubit extends Cubit<UserState, UserProps> {
  constructor() {
    super({ name: '', email: '' });

    // Subscribe to state changes
    this.onSystemEvent('stateChanged', ({ state, previousState }) => {
      console.log('State:', previousState, '->', state);
    });

    // Subscribe to props updates
    this.onSystemEvent('propsUpdated', ({ props, previousProps }) => {
      console.log('Props:', previousProps, '->', props);
    });

    // Subscribe to disposal
    this.onSystemEvent('dispose', () => {
      console.log('Cleaning up...');
    });
  }
}

State Changed

Fired after every state change via emit(), update(), or patch():

typescript
class AnalyticsTrackingCubit extends Cubit<AppState> {
  constructor() {
    super(initialState);

    this.onSystemEvent('stateChanged', ({ state, previousState }) => {
      // Track specific state changes
      if (state.user !== previousState.user) {
        analytics.track('user_changed', { user: state.user });
      }

      if (state.cart.items.length !== previousState.cart.items.length) {
        analytics.track('cart_updated', { itemCount: state.cart.items.length });
      }
    });
  }
}

Props Updated

Fired when updateProps() is called (typically by useBloc when props change):

typescript
class UserProfileCubit extends Cubit<ProfileState, { userId: string }> {
  constructor() {
    super({ profile: null, isLoading: false });

    this.onSystemEvent('propsUpdated', ({ props, previousProps }) => {
      // Reload when userId changes
      if (props.userId !== previousProps?.userId) {
        this.loadProfile(props.userId);
      }
    });
  }

  loadProfile = async (userId: string) => {
    this.patch({ isLoading: true });
    const profile = await api.getProfile(userId);
    this.patch({ profile, isLoading: false });
  };
}

Dispose

Fired when the instance is being disposed. Use for cleanup:

typescript
class WebSocketCubit extends Cubit<SocketState> {
  private socket: WebSocket | null = null;

  constructor() {
    super({ connected: false, messages: [] });

    this.onSystemEvent('dispose', () => {
      // Clean up WebSocket connection
      if (this.socket) {
        this.socket.close();
        this.socket = null;
      }
    });
  }

  connect = (url: string) => {
    this.socket = new WebSocket(url);
    this.socket.onopen = () => this.patch({ connected: true });
    this.socket.onclose = () => this.patch({ connected: false });
  };
}

Common Patterns

Releasing Owned Dependencies

typescript
import { acquire, release } from '@blac/core';

class AppCubit extends Cubit<AppState> {
  // Own a dependency
  private notifications = acquire(NotificationCubit);

  constructor() {
    super(initialState);

    // Release on dispose
    this.onSystemEvent('dispose', () => {
      release(NotificationCubit);
    });
  }
}

Persisting State

typescript
class SettingsCubit extends Cubit<SettingsState> {
  constructor() {
    // Load from storage
    const saved = localStorage.getItem('settings');
    super(saved ? JSON.parse(saved) : defaultSettings);

    // Save on change
    this.onSystemEvent('stateChanged', ({ state }) => {
      localStorage.setItem('settings', JSON.stringify(state));
    });
  }
}

Logging State Changes

typescript
class DebugCubit extends Cubit<State> {
  constructor() {
    super(initialState);

    if (process.env.NODE_ENV === 'development') {
      this.onSystemEvent('stateChanged', ({ state, previousState }) => {
        console.group(`${this.name} state changed`);
        console.log('Previous:', previousState);
        console.log('Current:', state);
        console.groupEnd();
      });
    }
  }
}

Multiple Handlers

You can register multiple handlers for the same event:

typescript
class MyCubit extends Cubit<State> {
  constructor() {
    super(initialState);

    // Handler 1: Analytics
    this.onSystemEvent('stateChanged', ({ state }) => {
      analytics.track('state_change', state);
    });

    // Handler 2: Logging
    this.onSystemEvent('stateChanged', ({ state }) => {
      logger.debug('State:', state);
    });
  }
}

See Also

Released under the MIT License.