Skip to content

RFC: Expose Event Payload in Redux DevTools via withTrackedReducer #294

@OleksandrOleniuk

Description

@OleksandrOleniuk

RFC: Expose Event Payload in Redux DevTools via withTrackedReducer

1. Summary

When using withTrackedReducer, the Redux DevTools extension currently logs only the string name of a dispatched event (e.g., [User] Update Age). The associated payload is silently discarded, leaving developers without visibility into what data drove a state transition.

This RFC proposes three coordinated, non-breaking internal changes to surface the full event object — including its payload — in the DevTools action log:

  1. Refactor withTrackedReducer to forward the full event object instead of extracting only event.type.
  2. Add an overload to the internal updateState utility that accepts an EventInstance (any object with a type: string property) as its action descriptor.
  3. Update DevtoolsSyncer to pass that full object to the Redux DevTools extension, making payload data visible alongside the action type.

2. Motivation

2.1 Developer Experience Gap

NgRx Signal Store's withTrackedReducer is designed to bring reducer-style determinism to the Signal Store. However, the current DevTools integration surfaces only half of the picture: the name of what happened, but not what happened. In practice, this forces developers to fall back to console.log statements inside their reducers to inspect the payload driving each transition — exactly the kind of manual introspection that DevTools is meant to eliminate.

2.2 Parity with Traditional NgRx

In a classic NgRx Store setup backed by @ngrx/store-devtools, every dispatched action appears in the DevTools panel with its full serialized payload. A developer can click any entry in the action log and immediately inspect the data. withTrackedReducer should provide the same level of observability; the current behaviour is a silent regression in DX for teams migrating from, or running alongside, the traditional NgRx store.


3. Design

3.1 — withTrackedReducer: pass the full event, not just the type

// Before
updateState(store, event.type, nextState);

// After
updateState(store, event, nextState);

3.2 — updateState: add an object overload

// Existing overload — unchanged
export function updateState<State extends object>(
  stateSource: WritableStateSource<State>,
  action: string,
  ...updaters: Array<
    Partial<NoInfer<State>> | PartialStateUpdater<NoInfer<State>>
  >
): void;

// New overload
export function updateState<State extends object>(
  stateSource: WritableStateSource<State>,
  eventInstance: EventInstance<string, any>,
  ...updaters: Array<
    Partial<NoInfer<State>> | PartialStateUpdater<NoInfer<State>>
  >
): void;

// Implementation normalises both shapes
export function updateState<State extends object>(
  stateSource: WritableStateSource<State>,
  eventOrEventType: string | EventInstance<string, any>,
  ...updaters: Array<
    Partial<NoInfer<State>> | PartialStateUpdater<NoInfer<State>>
  >
): void {
  if (typeof eventOrEventType === 'string') {
    currentActionNames.add(eventOrEventType);
  } else {
    currentEvents.add(eventOrEventType);
  }
  return originalPatchState(stateSource, ...updaters);
}

3.3 — DevtoolsSyncer: forward the object to the extension

// Before
this.#connection.send({ type }, this.#currentState);

// After — Redux DevTools natively supports action objects
this.#connection.send({ type, payload }, this.#currentState);

Result in DevTools:


// Before   
       
▶ [User] Update Age

type: "[User] Update Age"

// After

▶ [User] Update Age

type: "[User] Update Age"
payload: {
    userId: "u_4821"
    age: 31
}


4. Adoption Strategy

Strictly non-breaking. The string overload is preserved. No consumer code changes. No migration guide needed. Safe to ship as a minor release.


5. Implementation Plan

I have a working local fork with all three changes verified against the Chrome DevTools extension. The PR will include unit tests for both updateState overloads and E2E verification that the payload appears correctly in the action log.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions