Reputation: 505
I have a complex web app state which needs to keep track of various things including currently selected items, id's of items to display, etc. The web app can also have multiple of these "States" with only 1 active at a time.
For example I have 3 "tabs", depending on the tab selected will determine which state to load all the properties from. So effectively I have 3 instances of the same state with different values stored.
How do I structure this? Currently I've done it as an array but it definitely doesn't seem effective as angular re-renders everything inside my state object any time I change a single thing.
Here's an example of the current state structure:
GlobalState {
tabStates: TabState[]
selectedTabId: number
}
TabState {
tabId: number
selectedItem: string
otherSelectedItems: string[]
}
My tab state contains a fair bit more but I tried to simplify it.
Should I break each property inside the TabState out into its own state which for example for the selectedItem property would look something like this?
SelectedItemState {
selectedItems: { [tabId: number]: string };
}
Any advice would be appreciated, please respond if my question isn't clear enough thank you.
UPDATE 1
MY selector is not returning the new value unless I spread the whole featureState. I only want to spread on the single property of the state and have the selector recognise this, what am I doing wrong?
I have the following state
appState: {
feature1State: {
tabIds: number[],
selectedTabId: number,
items: Item[], // Yes I will change this to entity pattern
selectedItemId: {[tabId: number]: id}
}
}
And my feature reducer for the feature1State:
UPDATE_SELECTED_ITEM: {
feature1State.selectedItemId = { ...feature1State.selectedItemId };
feature1State.selectedItemId[feature1State.selectedTabId] = action.payload.id;
return feature1State;
}
And the selectors
export const getFeature1State = createFeatureSelector<Feature1State> .
('feature1State');
export const getSelectedIdForActiveTab = createSelector(
getFeature1State,
(state: Feature1State): number =>
state.selectedItemId[state.selectedTabId]);
Upvotes: 0
Views: 59
Reputation: 5707
Definitely best to avoid arrays for anything you want to modify. If you need to keep things in order you can keep an array of id strings though. (having said that in an app I work on we keep arrays of items as pages we want to display, but the items are just references to values in an object).
state: {
tabStates: {
[tabId]: {
tabId: number
selectedItemId: string
otherSelectedItems: {
[itemId]: string
}
},
selectedTabId: number
},
}
UPDATE 1
**I'm not familiar with using Angular with Redux or if there are differences to React
Your reducer should be a function that takes state and returns a new state object without mutating the existing state which your reducer is doing.
For the state shape I suggested (as I'm having trouble understanding the intricacies of yours) the reducer to update the selected tab's selected item would be
(tabStates, action) => {
return {
...tabStates,
[tabStates.selectedTabId]: {
...tabStates[tabStates.selectedTabId],
selectedItemId: action.payload.id
}
}
}
(I modified my suggested state to include selectedTabId in tabStates)
This could be simplified by using immer which will do the same as above.
import produce from "immer";
(tabStates, action) => {
return produce(tabStates, draft => {
draft[tabStates.selectedTabId].selectedItemId = action.payload.id
});
}
The process creates a new state object containing references to all of the properties of the original state and then replacing only the values that need to change with new objects/values.
For your selectors you should only need to use reselect where the selector does a lot of computations or where it uses map
or reduce
or similar which produce a new object every time which would cause a re-render, even if the values contained in the object have not changed.
In this case you can use a very simple selector as you are returning a value from state directly.
(state: TabStates): number => state[state.selectedTabId].selectedItemId);
Upvotes: 1