[Fixed] NgRx: unrelated select firing when updates another slice of state

Issue

I have a "feature" state in my NxRx (v7) enabled application, that looks like the following..

State

    export interface DataPageState {  
        dataReceivedTimestamp: number; 

        formData: PageSaveDataState;      // <--- setting this

        previewDataHeaderNames: string[]  // } <--- calls selector for these
        previewData: string[][];          // }

        error: string;
        isLoading: boolean;  
    }

Selectors

I have a selector to get both previewDataHeaderNames and previewData – returned as an object as I use them to populate a data grid

export const getDataPreviewFeatureState = createFeatureSelector<DataPreviewPageState>(NgRxStateNames.dataPreviewPage);

    /** Get preview data */
    export const getPreviewData = createSelector(
        getDataPreviewFeatureState,
        (state: DataPreviewPageState) => {
            return { previewData: state.previewData, headers: state.previewDataHeaderNames };
        }
    );

Reducer

In my reducer, I have the following to update the formData slice of state. I Have a form on the same page and I use this to save its state (as well as data grid layout etc)

case myActions.ActionTypes.SetPreviewFormData: {
    this.logger.info('SetPreviewFormData');
    const newState = { ...action.payload, timeStamp: this.timeService.getUtcNowTimestamp() }
    return { ...state, formData: newState };
  }

Selector firing when I set the formData

Finally, in my component code, I have a selector for the getPreviewData

  this.subs.sink = this.store$.select(fromDataPreview.getPreviewData).subscribe(previewData => {      
    this.loadGridData(previewData.headers, previewData.previewData);
  });

When I route to another page, just before I route I call an action to set the form data as per the case myActions.ActionTypes.SetPreviewFormData above.

But when I call this, I have the above selector fire, and so my method to set the (already set) grid data is called again. If I comment out my dispatch to myActions.ActionTypes.SetPreviewFormData this is behaviour no longer happens.

My question is why does setting one slice of the state fire a select different slice? From what I understand the whole idea of the selectors was to only get the notification when the property involved in that selector, otherwise may as well just have one selector that returns the whole state.

Any idea why this is happening, or am I wrong in my assumption?

Thanks in advance!

Solution

I would have to double check, but my assumption would be:
Your reducer returns a complete new object for the state thru the spreading syntax (which is correct):

 return { previewData: state.previewData, headers: state.previewDataHeaderNames };

Now, the selectors cache resets and all selectors are recomputed.
See e.g. https://medium.com/swlh/memoization-with-selectors-in-ngrx-c60e67a08161, explains it rather well imo.

I think you might achieve what you want with the following pattern:

First, define two selectors for the two single state attributes you require:

export const getPreviewData = createSelector(
    getDataPreviewFeatureState,
    (state: DataPreviewPageState) => state.previewData 
    }
);

export const getPreviewDataHeaderNames = createSelector(
    getDataPreviewFeatureState,
    (state: DataPreviewPageState) => state.previewDataHeaderNames 
    }
);

Then combine these two selectors in order to define the selector for your component:

    export const getPreviewDataWithNames = createSelector(
        getPreviewData,
        getPreviewDataHeaderNames,
        (previewData, previewDataHeaderNames) => ({ previewData, previewDataHeaderNames })
        
    );

Now the following should occur: Since the references for previewData and previewDataHeaderNames do not change and the selector has no direct reference to your state, your combined selector does not recompute.

Leave a Reply

(*) Required, Your email will not be published