Encountering NgRx Select Issues While Trying to Access Nested Properties

I've encountered TypeErrors while using NgRx select functions to access nested properties.

In my app.module.ts, I have configured the root store as follows:

StoreModule.forRoot({ app: appReducer }),

The selectors causing errors are related to some nested properties:

const getAppFeatureState = createFeatureSelector<IAppState>('app');

export const getAppConfig = createSelector(getAppFeatureState, state => {
  return state.appConfig.data;
});

export const getConfigControls = createSelector(getAppConfig, state => {
  console.log({ state }) // logs values from initial state
  return state.controls;
});

export const getConfigDropdowns = createSelector(
  getConfigControls,
  state => state.dropdowns,
);

When subscribing to these selectors in app.compontent.ts like so:

ngOnInit() {
  this.store.dispatch(new appActions.LoadAppConfig());
  this.store
    .pipe(select(appSelectors.getConfigDropdowns))
    .subscribe(data => {
      console.log('OnInit Dropdowns Data: ', data);
    });
}

app.component.ts:31 ERROR TypeError: Cannot read property 'dropdowns' of null
      at app.selectors.ts:18

Adding logging revealed that only the initialState values were being logged. This leads me to believe that the selector function should not fire until the value changes. However, since it doesn't, the error occurs when trying to access a property on null. Does the initialState need to include all potential future nested properties for the selectors to work correctly?

How can I prevent the selector from firing when its value remains unchanged?

Furthermore, is the configuration of StoreModule.forRoot correct? It seems unusual to me that creating a "root" store places the app key parallel to module stores, rather than underneath it.

Edit:

I'll also provide an overview of the structure in app.reducer.ts. I use immer to simplify updating nested properties, although I have tested the reducer without immer with similar results.

import produce from 'immer';

export const appReducer = produce(
  (
    draftState: rootStateModels.IAppState = initialState,
    action: AppActions,
  ) => {
    switch (action.type) {
      case AppActionTypes.LoadAppConfig: {
        draftState.appConfig.meta.isLoading = true;
        break;
      }
      /* more cases updating the properties accessed in problematic selectors */
      default: {
        return draftState;
      }
    }
}

Edit: Add initialState:

const initialState: rootStateModels.IAppState = {
  user: null,
  appConfig: {
    meta: {isError: false, isLoading: false, isSuccess: false},
    data: {
      controls: {
        dropdowns: null,
      }
    },
  },
};

Answer №1

As a result of updating your query, the solution can be found at https://www.learnrxjs.io/learn-rxjs/operators/filtering/distinctuntilchanged

This method ensures that values are only emitted when they have changed.

store.pipe(
  map(state => state.feature.something),
  distinctUntilChanged(),
)

This approach necessitates that state.feautre.something has indeed been altered.

A more effective technique would involve utilizing the createSelector function, which produces memoized selectors that operate in a similar fashion to distinctUntilChanged.

Answer №2

To ensure that only valid values are emitted, you can utilize the filter operator. Afterwards, you can employ the pluck operator to extract the value of the specified nested property.

store.pipe(
  filter(value => state.feature.something),
  pluck('feature', 'something'),
)

Answer №3

The dispatch method operates asynchronously. Therefore:

ngOnInit() {
  this.store.dispatch(new appActions.LoadAppConfig());
  this.store
    .pipe(select(appSelectors.getConfigDropdowns))
    .subscribe(data => {
      console.log('OnInit Dropdowns Data: ', data);
    });
}

In this scenario, the subscription executes faster than the dispatch, causing the select to return null value from the initial state. To address this, you can either verify this in the selector or include an initial state. For example:

const getAppFeatureState = createFeatureSelector<IAppState>('app');

export const getAppConfig = createSelector(getAppFeatureState, state => {
  return state.appConfig.data;
});

export const getConfigControls = createSelector(getAppConfig, state => {
  console.log({ state }) // logs values from initial state
  return state.controls;
});

export const getConfigDropdowns = createSelector(
  getConfigControls,
  state => state ? state.dropdown : null,
);

Answer №4

Upon reviewing the code once more, I have made some updates to my answer.

Could you please test out the sample provided below?

this.store
  .pipe(
    // In this section, 'isStarted' will determine whether the selector is enabled or disabled.
    // This value can be obtained from the initial state - if it's null, it won't proceed to the next selector.
    switchMap(data => {
      if (isStarted) {
        return never();
      } else {
        return of(data);
      }
    }),
    switchMap(data => select(appSelectors.getConfigDropdowns))
  )
  .subscribe(data => {
    console.log("OnInit Dropdowns Data: ", data);
  });

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

If the selected tab matches, collapse it using jQuery

When utilizing the jQuery accordion, I am looking to collapse if the currently active tab is selected. If I click on the same tab again, nothing happens. However, if I click on another tab, the activated tab changes accordingly. Check out an example on j ...

Expand the font manually

Is there a way to define a type that represents the widened version of another type? Consider the following scenario: function times<A extends number, B extends number>(a: A, b: B): A & B; The intention behind this times function is to preserv ...

"Exploring the v-autocomplete functionality: Recognizing when no results are found and incorporating a personalized

I am using a Vuetify component to search for users in a list. If the search yields no results, I want to display a button that allows the user to create a new user: <v-autocomplete v-model="event.user" :items=" ...

Tips for personalizing the color scheme of Material UI Stepper Step?

I need help customizing the disabled Step color for Material UI Steppers. The default colors are Blue for enabled steps and Grey for disabled steps, but I want to change it to a different color as shown in this example: https://i.stack.imgur.com/HGGxp.png ...

Tips for preventing the direct copying and pasting of JavaScript functions

Repeating the process of copying and pasting functions such as loadInitialValue(), loadInitialValue2(), loadInitialValue3(), is quite monotonous. This is the repetitive code snippet that I have been working on (when you click on "Mark as Read," the title o ...

Cannot extract the 'name' property from 'e.target' because it is not defined

I encountered an error message stating that I cannot destructure the property 'name' of 'e.target' because it is undefined within the createform() method. Despite highlighting the line causing the error, I am still unable to comprehend ...

Scrolling to an id element in Vue.js can be achieved by passing the ID in the URL using the "?" parameter. This

My challenge involves a URL http://localhost:8080/?london that needs to load directly to the element with an id of london in the HTML section <section id="london"> on the page. Using http://localhost:8080/#london is not an option, even though it woul ...

Troubleshooting: The issue of ngModel being undefined in Angular2 with the <input> element

I am currently working with Angular2 and a modified version of Semantic-UI that includes a calendar module. I am utilizing the `calendar` and `dropdown` functionalities: constructor() { setTimeout(() => { jQuery('.ui.dropdown').dr ...

The localStorage attribute is incapable of storing the href property of an element

When using the localStorage property like localStorage.setItem('theme', element);, keep in mind that it does not store the href property of an element such as element.href = '../assets/css/syntax-highlighting/synthwave-84.css';: const l ...

Querying arrays using HTTP Get request in nodejs

When attempting to access array data through an HTTP GET call with headers in NodeJS, I am encountering an undefined error when calling third-party services. The HTTP GET call includes two headers. This is the response from the HTTP GET call: How can I acc ...

I'm experimenting with incorporating images into a card component using the map function in JavaScript. All aspects of the card are functioning properly except for the image

import React from 'react' import Card from '@material-ui/core/Card'import CardMedia from '@material-ui/core/CardMedia'; import CardContent from '@material-ui/core/CardContent'; import {makeStyles} from '@materia ...

Breaking apart values separated by semicolons in JavaScript to create separate rows with additional information

Here is the provided data: result = [ { "Id":"0012v00002InPVmAAN", "Test__c":"India; Africa; Mombasa", "Test1__c":"AFR; TFR; GFR" } ] I want to convert the above data into a CSV file by splitting t ...

What is the method to activate map dragging in Leaflet only while the spacebar is pressed?

When using Leaflet maps, the default behavior is to drag the view around by only clicking the mouse. However, I am interested in enabling dragging with the mouse only if the spacebar is pressed as well. I would like to reserve mouse dragging without the sp ...

Determine the type and create an instance of a fresh class

In my app, I have a function that handles all API requests. Any interaction I make goes through this function. I'm trying to set a specific return type for this function, but the return type is of a class. In order to use the methods of this class, I ...

In Vue.js, when attempting to arrange an array of objects in descending order based on a specific key (such as "name"), the intention is to prioritize data containing uppercase letters to be displayed

I am struggling to organize an array of objects based on a specific key (name). My goal is to have the data with uppercase letters appear first, but for some reason, it's displaying the lowercase data first. I've been using the lodash method "ord ...

Tips on preventing the copying of .txt and .xml files with the fs-extra.copySync function

Currently, I am working on a small TypeScript assignment and facing an issue that I can't seem to solve. Any guidance or advice on the problem mentioned below would be greatly appreciated. The task at hand involves copying a directory from one locati ...

Is it possible to define a constant enum within a TypeScript class?

I am looking for a way to statically set an enum on my TypeScript class and be able to reference it both internally and externally by exporting the class. As I am new to TypeScript, I am unsure of the correct syntax for this. Below is some pseudo-code (whi ...

I utilized the explode function in PHP to transform a string into an array. Now, I require assistance with manipulating

Currently, I am facing a challenge where I have converted a string into an array in PHP using explode. I need to pass this array to a separate JavaScript page and then access the data values from within. Below is the structure of the array in JavaScript o ...

Exploring dynamic data with Highcharts geomaps

API is being called to display data in the chartOptions. However, I am encountering an issue trying to pass it through this.letsTry. I am unsure where I am making a mistake. [data-local.component.html] <highcharts-chart id="container" [Highch ...

Handling exception type in child_process exec method - NodeJS with Typescript integration

Check out this sample code: const execPromise = util.promisify(exec); try { const { stdout } = await execPromise(cliCommand); } catch (error) { if (error instanceof S3ServiceException) { // error message is not handled correctly console ...