Reputation: 7630
I have an NGRX store looking like this :
export interface INavigationSettings {
gridLayout: {
[Breakpoints.Small]: GridLayout;
[Breakpoints.Large]: GridLayout;
};
//...
}
I have an action that will apply modification to those GridLayout
const SET_NAVIGATION_GRID_VISIBILITY = (state: State, action: featureAction.SetNavigationGridVisibility) => {
state.navigation.gridLayout[action.payload.size].visibility = {
...state.navigation.gridLayout[action.payload.size].visibility,
...action.payload.visibility
};
return state;
};
This change is correctly applied in the store
The problem is, my selector selectNavigationGridLayout
export const selectSettingsState: MemoizedSelector<object, State> = createFeatureSelector<State>('settings');
export const gridLayout = (state: State): {
Small: featureModels.GridLayout;
Large: featureModels.GridLayout;
} => state.navigation.gridLayout;
export const selectNavigationGridLayout: MemoizedSelector<object, {
Small: featureModels.GridLayout;
Large: featureModels.GridLayout;
}> = createSelector(selectSettingsState, gridLayout);
Never catch any changes, and do not call the change state trough the app. It was working fine before when I had a single object gridLayout, but since I am doing mobile, I separated in 2 pieces =>
gridLayout: {
[Breakpoints.Small]: GridLayout;
[Breakpoints.Large]: GridLayout;
};
and now it never triggers.
I also tried to
return {
...state
}
EDIT :
I changed to this
const SET_NAVIGATION_GRID_VISIBILITY = (state: State, action: featureAction.SetNavigationGridVisibility) => {
return {
...state,
navigation: {
...state.navigation,
gridLayout: {
...state.navigation.gridLayout,
[action.payload.size]: {
...state.navigation.gridLayout[action.payload.size],
visibility: {
...state.navigation.gridLayout[action.payload.size].visibility,
...action.payload.visibility
}
}
}
}
};
};
and it works, but it's terrible, isn't there a better way ?
Upvotes: 1
Views: 378
Reputation: 529
Your last edit works because you are returning new state, not mutating the existing state.
Here are some "prettier" solutions to return new state.
Another solution is to use ActionReducerMap
to decompose your reducers into being focused on a particular piece of state.
I see that your top-level feature is named settings
. So your store looks a little like this:
interface StoreState {
settings: SettingsFeatureState;
}
interface SettingsFeatureState {
navigation: INavigationSettings;
}
interface INavigationSettings {
gridLayout: GridLayoutState;
}
interface GridLayoutState {
[Breakpoints.Small]: GridLayout;
[Breakpoints.Large]: GridLayout;
}
And your settings reducer looks like one of the two:
function settingsReducer(state: SettingsFeatureState, action: Action): SettingsFeatureState {
// ...
}
// or
function navigationReducer(state: INavigationSettings, action: Action): INavigationSettings {
// ...
}
const settingsReducer: ActionReducerMap<SettingsFeatureState> = {
navigation: navigationReducer
};
Do the following steps to decompose your state reducers even more.
Create a grid layout reducer like this:
function gridLayoutReducer(state: GridLayoutState, action: Action): GridLayoutState {
// ...
}
const SET_NAVIGATION_GRID_VISIBILITY = (state: GridLayoutState, action: featureAction.SetNavigationGridVisibility): GridLayoutState => {
return {
...state,
[action.payload.size]: {
...state[action.payload.size],
visibility: {
...state[action.payload.size].visibility,
...action.payload.visibility
}
}
};
};
Then, modify your navigationReducer register the gridLayoutReducer
as follows:
const navigationReducerMap: ActionReducerMap<INavigationSettings> = {
gridLayout: gridLayoutReducer
}
// This function has the following signature:
// navigationReducer(state: INavigationSettings, action: Action): INavigationSettings
const navigationReducer = combineReducers(navigationReducerMap)
If you really don't want to return new state, you could keep your original logic with a slight modification using something like lodash
to deep clone the state:
const SET_NAVIGATION_GRID_VISIBILITY = (state: State, action: featureAction.SetNavigationGridVisibility) => {
const newState = _.deepClone(state)
newState.navigation.gridLayout[action.payload.size].visibility = {
...newState.navigation.gridLayout[action.payload.size].visibility,
...action.payload.visibility
};
return newState;
};
This returns new state since you've completely cloned the state. This approach will use a lot more resources, however, since you're cloning the entire state just to modify a few properties.
Upvotes: 1