Cannot read property of map of undefined when using featureSelector ngrx
For people who come across this in the future this can be solved by refactoring the selectors slightly.
As per the (somewhat incomplete at the time of posting) docs here, ngrx.io/entity/adapter, you need to select the featureSate using the createFeatureSelector
function and then use that substate with the adapter selectors to select slices of that state.
Change this:
post.reducer.ts
export const getPostState = createFeatureSelector<PostState>('post');
export const {
selectIds,
selectEntities,
selectAll: selectAllPosts,
selectTotal,
} = postAdapter.getSelectors(getPostState);
to this:
export const selectPostState = createFeatureSelector<PostState>('post');
export const {
selectIds,
selectEntities,
selectAll
selectTotal,
} = postAdapter.getSelectors();
export const selectPostIds = createSelector(
selectPostState,
selectIds
);
export const selectPostEntities = createSelector(
selectPostState,
selectEntities
);
export const selectAllPosts = createSelector(
selectPostState,
selectAll
);
This will select the substate of the postState
feature.
Hope this helps
Edit: So it appears the above two examples are equivalent and the issues I was facing was the same as Matthew Harwood and it was improper use of the ActionReducerMap
.
I think the docs might be a bit confusing on this as they lead to believe that the state of and entity is the same thing as the state of a feature state such as a lazy loaded state.
While an entity state has properties such as id's and entities, they do not need a reduce each like feature state does but only one reducer to cover all the entity state for that model.
e.g.
export const initialState: PostState = postAdapter.getInitialState(postInitialState);
does not need a reducer map for the PostAdapterState
because the one reducer
export function postReducer(state: PostState = initialState, action:
PostActions): PostState {
switch (action.type) {
case PostActionTypes.ADD_ONE:
return postAdapter.addOne(action.post, state);
default:
return state;
}
}
Covers all the entity state updating.
You would need a reducer map if you multiple adapter states in a feature state such as the example below.
Lets say you had a Library feature state that and entities of books and magazines. It would look something like the below.
book.reducer.ts
export interface BookEntityState extends EntityState<Book> {
selectedBookId: string | null;
}
const bookAdapter: EntityAdapter<Book> = createEntityAdapter<Book>();
export const initialBookState: BookEntityState = adapter.getInitialState({
selectedBookId: null
});
export const bookReducer (...) ... etc
magazine.reducer.ts
export interface MagazineEntityState extends EntityState<Magazine> {
selectedMagazineId: string | null;
}
const magazineAdapter: EntityAdapter<Magazine> = createEntityAdapter<Magazine>();
export const initialMagazineState: MagazineEntityState =
adapter.getInitialState({
selectedMagazineId: null
});
export const magazineReducer (...) ... etc
And then you library feature state may be something like
library.reducer.ts
// Feature state,
export interface LibraryFeatureState {
books: BookEntityState
magazines: MagazineEntityState
}
// Reducer map of the lirbray
export const libraryReducersMap: ActionReducerMap<LibraryFeatureState> = {
books: bookReducer,
magazines: magazineReducer
};
And then adding the feature state to your Store in the LibraryModule imports array.
library.module.ts
...
StoreModule.forFeature<LibraryFeatureState>('libraryState',
libraryReducersMap),
...
Hope this helps someone else.
I ran into the same problem trying to create a selector with the following
export const selectFuturMenus = createSelector(
selectMenuState,
fromMenu.selectAll,
(state, menus: Menu[], props) => menus.filter(menu => {
let startMmt = moment(menu.start_date, 'YYYY-MM-DD');
let endMmt = moment(menu.end_date, 'YYYY-MM-DD');
let mmt = moment();
console.log(startMmt, endMmt);
return mmt.isBefore(endMmt);
})
);
It seems we can't use an adapter selector before a filter function. Following the NgRX documentation a simple solution is to decompose the selector like this
export const selectAllMenus = createSelector(
selectMenuState,
fromMenu.selectAll,
);
export const selectFuturMenus = createSelector(
selectAllMenus,
(menus: Menu[], props) => menus.filter(menu => {
let startMmt = moment(menu.start_date, 'YYYY-MM-DD');
let endMmt = moment(menu.end_date, 'YYYY-MM-DD');
let mmt = moment();
return mmt.isBefore(endMmt);
})
);