Understanding createSlice
+ selectors
+ memoization Current State
#4553
-
Note: I tried to follow the conversation in #3387 as well as the documentation on createSlice, createSelector, and Deriving Data with Selectors. It is very likely the answer is spelled out for me somewhere in all of those, but I am having a hard time distilling everything into the correct approach. So here we are and thank you in advance... Context:I have a slice which maintains entitlement data: export interface ResolvedEntitlements {
[boundary: string]: ResolvedBoundaryEntitlements;
}
export interface AuthenticationState {
entitlements?: { [boundary: string]: { [scope: string]: ResolvedBoundaryEntitlements } };
}
const initialState: AuthenticationState = { };
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
...
}
}); The way we manage entitlement data is not perfect. Without getting into a detailed discussion on how we manage entitlements, there are top level boundaries (eg. user or organization) and sub-system "scopes". We do not have a singular representation of entitlements in a database somewhere. Instead, sub-systems manage their own so we end up with a map of maps data structure. What I would like:The entitlement data is global to the application, so I would like to write a selector which merges all of the partials into a singular map. The selector I have looks something like: export const authSlice = createSlice({
...
selectors: {
SelectEntitlements: (state: AuthenticationState): ResolvedEntitlements => {
const entitlements = state.entitlements;
if (!entitlements) {
return {};
}
const resolved: ResolvedEntitlements = {};
Object.keys(entitlements).forEach((boundary) => {
const scopedEntitlements = entitlements[boundary];
Object.values(scopedEntitlements).forEach((scoped) => {
resolved[boundary] = mergeResolvedBoundaryEntitlements(resolved[boundary], scoped);
});
});
return resolved;
}
}
}); My understanding is that every time I reference Further, I do not need component specific selector functions. The entitlement state is global so it should always be the same across the application unless the underlying state has changed. (Note: I understand that because the selectors object is "instantiated" every time I reference The issue:
Which, to be clear, is obvious, the selector function is returning a new object reference every time. Questions
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
Hi there!
This is not the case - the first time
There's no "auto-magic" with the wrapped selectors. They're essentially as simple as In this case, it seems like you'd want something like the below: export const authSlice = createSlice({
...
selectors: {
selectEntitlements: createSelector(
(state: AuthenticationState) => state.entitlements,
(entitlements): ResolvedEntitlements => {
const entitlements = state.entitlements;
if (!entitlements) {
return {};
}
const resolved: ResolvedEntitlements = {};
Object.keys(entitlements).forEach((boundary) => {
const scopedEntitlements = entitlements[boundary];
Object.values(scopedEntitlements).forEach((scoped) => {
resolved[boundary] = mergeResolvedBoundaryEntitlements(resolved[boundary], scoped);
});
});
return resolved;
}
),
}
}); This would make it so that your result is only recalculated when |
Beta Was this translation helpful? Give feedback.
-
Apparently I have no manners... Thank you! That was exactly the solution, as feared it was right in front of my face. ❤️ |
Beta Was this translation helpful? Give feedback.
Hi there!
This is not the case - the first time
.selectors
is referenced, it'll make a wrapped version of the selectors object. Every time after that it'll return the same cached object. The same goes forgetSelectors(selectSlice)
- if the sameselectSlice
callback is passed, it'll return the same object.