NgRx, returning the combination of 2+ states - ngrx

I have a website with virtual currency.
I want to disable all tools that include transactions of any sort when one is on progress (betting on a contest, withdrawing, depositing money etc…).
To this purpose I would like to have a single selector that returns a boolean specifying if there are transactions taking place.
F.E.
export const getTransactionsRunning = createSelector(
getState,
getPartOfTheState,
getPossiblyAPartOfTheStateNotInThisReducer,
(state: StateSomehowMerged?) => state.case1 || state.case2 || state.case3,
);
Is it possible to do anything like that natively with NgRx, or do I have to create a function to merge the observables of the 3?

Related

Proper way of using Redux and RTKQ in NextJs with code-splitting

This is a topic that's been discussed a lot through github issues and by now I've noticed two main opinions: It's not possible or it should not be done at all.
The argument for both sides is that redux is not meant for it, that the .replaceReducer function is only meant for the purposes of hot-reloading (even though redux itself mentions it as a possibility for code-splitting).
The goal
Anyway, what I would like to achieve (ideally) is a system that only sends the relevant slices and relevant redux code for a specific route in NextJs. And (even more ideally) when navigating between pages the store should just get extended and not re-created.
My initial approach
My first idea was to implement a recipe from the link above, attaching and exposing the injectReducer function onto my store during the store setup:
const store = configureStore({
reducer: {
globals,
[rtkqApi.reducerPath]: rtkqApi.reducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(rtkqApi.middleware)
});
store.dynamicReducers = {};
store.injectDynamicReducer = (name, reducer) => {
if (Object.keys(store.dynamicReducers).includes(name)) {
return;
}
store.dynamicReducers[name] = reducer;
store.replaceReducer(
combineReducers({
globals,
[rtkqApi.reducerPath]: rtkqApi.reducer,
...store.dynamicReducers
})
);
};
const makeStore = () => store;
export const wrapper = createWrapper(makeStore);
export const injectReducer = (sliceName, reducer) => store.injectDynamicReducer(sliceName, reducer);
So basically every page would have a globalsSlice, containing the user info and some other global data, and Redux Toolkit Query API slice (which would then be code-split using RTKQ injectEndpoints functionality).
With this setup, each page that wants to inject its own custom slice (reducer) would do something like this:
const SomePage = () => {
const someData = useSelector(somePageSliceSelectors.selectSomeData);
return (
<Fragment>
<Head>
<title>Some Page</title>
</Head>
</Fragment>
)
};
export default SomeRoute;
injectReducer('somePageSlice', somePageReducer);
export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
// Whatever necessary logic we need
});
Initially this seemed to have worked fine, but then when I realized that next-redux-wrapper works by calling the makeStore factory on every request, and I'm manipulating and mutating a global store object, there has to be something wrong with this, ie a race condition that I haven't been able to cause by testing. Also another problem occurres when using Redux Toolkit Query. For example, if I need to get a cookie from the original request (the one that nextjs receives) and then re-send it to another API endpoint that is handled by redux toolkit query, I would need to extract the cookie from the request context, to which I don't have access unless I do something like this:
export const makeStore = (ctx) => {
return configureStore({
reducer: ...,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: {
extraArgument: ctx,
},
}).concat(...),
});
};
which further implies that I should definitely not be mutating the global store object.
So then I thought alright, instead of manipulating the global store I could try doing it in GSSP:
export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
store.injectDynamicReducer('somePageSlice', somePageReducer);
});
But no luck here, the slice does not get loaded and the state does not get constructed. It is my guess that the Provider in the _app gets rendered before this, but I'm not sure.
In conclusion, I'd like to know whether anyone has tried and succeeded in implementing redux code splitting using RTK, RTKQ and NextJs. Also, I would like to ask an addition question: Is it necessary? What I mean by this is, if I were to not code-split at all, and send all slices on every request, how performance impactful would this be? Also, since I'm not sure exactly how the NextJs bundler works and how code chunking is done: If a certain page receives a slice it doesn't use at all, will it only receive its initial state or all of its logic (all the selectors, reducers and actions)? If not then maybe this isn't so bad, since initial states are just empty objects.
I hope I've presented the problem clearly enough, as it is a very complex problem, but feel free to ask follow up questions if something doesn't make sense.
Thanks in advance.

Best way to query for favorite list

users have the ability to set a item as favorite, when pressing the star icon they set of a Redux function that sets this as a local favorite and saves this in the Firebase Database as well;
-Userfavorites
--userID
---ItemID: true (or false if the disable the favorite later on)
This all works well, however I'm now trying to figure out how it is best to handle the information when it is retrieved from Firebase. On opening of the app all items are retrieved, as wel as the above UserFavorites list.
What is the best way to handle this in my Redux state?
Component: {ItemID1: true, ItemID2: true, ItemID3: false}
or
Array: 0.[ItemID1: true]1.[ItemID2: true]2.[ItemID3: false]
When opening an item I want to cross check the itemID that is opened, if it Exists in the UserFavorites list in Redux AND is set to true the icon should so different from a value of false.
What I have found is that I cannot look into the Redux state with a variable (like state.items.userfavorites.ItemID === true (Where ItemID is the variable of the Item ID that is opened.) What is your preferred way of working with a favorite function and am I on the right track with using the favorites list?
So I got this working yesterday. My Firebase Database still looks as above. Below is the Redux action. I create an array with this where every entry has the favID prefix followed by the key, which in my case is the itemID.
const resData = await response.json();
console.log('fetchFavorites action response data' , resData)
const result = Object.entries(resData).map(([key, val]) => ({
['favID']: key
}));
console.log('results' , result)
dispatch({
type: FETCH_FAVORITES,
userFavorites: result
});
Then on the detail page I use the following code to look if the currentItemIsFavorite.
const currentItemIsFavorite = useSelector(state =>
state.onlineItems.favoriteItems.some(item => item.favID === itemId)
);
After that generating the correct icon with this;
<Item
title="Favorite star"
iconName={isFavorite ? 'ios-star' : 'ios-star-outline'}
color='gold'
onPress={toggleFavorite}
/>

Will using selectors to compute derived data from an API call perform better than doing within the reducer? (for this use-case)

Say I have a music store app where the user searches for guitars. On initial page load, I fetch a few varieties of guitars to display: (acoustic, electric, and bass). Pages of guitar results are returned together from a single API call but will never be displayed together. Therefore, they must be filtered at some point. To view different categories of guitars, the user will toggle the category they view from a react component.
There seems to be two major ways I can approach this problem with immutable and redux.
In Strategy 1, I filter the data on category when it arrives, and store it separately in the redux store. When I want to retrieve the data, I specify the category in the selector.
In Strategy 2, all API data that comes in is stored in an aggregate List "all". When I want to retrieve a particular category of guitars, I used a selector to filter and display from the aggregated data.
STRATEGY 1:
// REDUCER
export const GuitarReducer = (state, action) => {
const { payload, type } = state;
switch (type) {
case "acoustic": {
let existing = // GET EXISTING
return state.set("acoustic",
existing.concat(payload.filter(result => (result.category === "acoustic")))
)
}
case "electric": {
let existing = // GET EXISTING
return state.set("electric",
existing.concat(payload.filter(result => (result.category === "electric")))
)
}
case "bass": {
let existing = // GET EXISTING
return state.set("bass",
existing.concat(payload.filter(result => (result.category === "bass")))
)
}
}
}
// SELECTOR
export const selectCategory = createSelector(
[getCategory, getGuitarReducer],
(category, guitarReducer) => {
return GuitarReducer.get(category);
}
);
STRATEGY 2:
// REDUCER
export const GuitarReducer = (state, action) => {
const { payload, type } = state;
...
let existing = // GET EXISTING
...
return state.set("all",
existing.concat(payload)
)
}
// SELECTOR
export const selectCategory = createSelector(
[selectAllGuitars],
(category, guitars) => {
return guitars.filter(guitar => (guitar.category = category));
}
);
Will one pattern give better performance than another? What pattern better follows best practices for redux?
I have heard that it is best to prefer selectors for computing derived data, and that memoization will cache the results to use when another action is performed on the data such as toggling between tabs. Because of this, it is not clear to me which strategy to prefer.
I think selectors mainly focused of not re-computing derived data in your components (and the benefit of reusing it across other components).
Both in your example are good practices, so I would reframe it as follows. Do you want your datastore to look like in choice one or choice two (original API response). Do you want it to lazily load (choice two), or load categories for all guitars.
Choice 1
Pros
Stores in datastore in format more useful to your application.
Choice two recomputes on category change, choice one is computed at start and most likely more performant.
Cons
No access to original API response.
Performs filtering and categorizing on API request instead of lazily (Honestly not a big problem).
Choice 2
Pros
Stores in datastore original API response.
Lazily computes the required guitar category.
Cons
Performs computation again on category change. (Note reselect only has a cache size of 1).
Memoizing also takes additional memory.

Ngrx Large Amounts of Data causes app to slow down

I have an app that loads some images with metadata. A single folder can be quite large (~100-142Mb) once loaded into memory. Previously, we were using a plain old javascript object to manage the state of the app and everything worked fine, but i'd like to gain the benefits of ngrx's state management.
I've discovered ngrx and it seemed to be a smarter option when it comes to state management. However, when i add these items to the state, the app hangs when adding images to the store and then performance slows down when accessing individual (and unrelated) flags from the store i.e. UI flag - draw is open.
1) Here "directories" is a Map < string, Directory > () object that is saved the the Store (~100-120Mb). Directory is a complex object with many nested values. Once images are loaded, and then added to the store, it a) hangs and then b) everything else (i.e. changing a ui flag) slows down.
return {
...state,
loadedDirectories: directories,
filesLoading: false,
};
2) The directories are then later accessed from the store.
this.store
.pipe(select(fromReducer.getLoadedDirectories))
.subscribe(loadedDirectories => {
this._directoryData = loadedDirectories;
});
Selector looks like this....
export interface ImageLoaderState {
loadedDirectories: Map<string, Directory>;
filesLoading: boolean;
errorMessage: string;
}
export class AppState {
imageLoader: fromImageLoader.ImageLoaderState;
}
export const combinedReducers = {
imageLoader: fromImageLoader.imageLoaderReducer
.... More reducers here ....
}
// Select Image loader state.
export const selectImageLoaderState = (state: AppState) => state.imageLoader;
export const getLoadedDirectories = createSelector(
selectImageLoaderState,
(state: fromImageLoader.ImageLoaderState) => state.loadedDirectories
);
Using angular 8 and the following versions of ngrx.
"#ngrx/effects": "^8.4.0",
"#ngrx/store": "^8.4.0",
"#ngrx/store-devtools": "^8.4.0",
Are there any better practices? i.e. Add each image, one at a time to the store?
The ngrx store is for application state and not so good as a document store.
Please see..
https://github.com/btroncone/ngrx-store-localstorage/issues/39
One issue I see is how you create your new state. You mention that when you create your new state, you do the following
return {
...state,
loadedDirectories: directories,
filesLoading: false,
};
I think you are creating an object with tons of key-value pairs, then recreating that work when you set the loadedDirectories property again. I'm uncertain about the performance costs of using the spread operator in the context of very large objects. I would suggest you focus on creating this property once. This might help you
Does spread operator affect performance?

Why the reselect createSelector necessary in this #ngrx example?

What does the following code snippet do? It is taken from this file.
export const getCollectionLoading = createSelector(getCollectionState, fromCollection.getLoading);
The fromCollection.getLoading has only either true or false value, so can there be any optimization achieved by using the createSelector?
in
export const getCollectionLoaded = createSelector(getCollectionState, fromCollection.getLoaded);
export const getCollectionLoading = createSelector(getCollectionState, fromCollection.getLoading);
export const getCollectionBookIds = createSelector(getCollectionState, fromCollection.getIds);
According to the example app's comments:
/**
* Every reducer module exports selector functions, however child reducers
* have no knowledge of the overall state tree. To make them useable, we
* need to make new selectors that wrap them.
**/
For example in reducers/collections.ts the selectors at the bottom of the file only reference the collection slice:
export const getLoaded = (state: State) => state.loaded;
export const getLoading = (state: State) => state.loading;
export const getIds = (state: State) => state.ids;
These collection selectors can't be used with state.select() because state.select expects to work with the whole AppState. Therefore in the reducers/index.ts the selector is then wrapped with another selector so that it does work with the whole AppState:
export const getCollectionLoading = createSelector(getCollectionState, fromCollection.getLoading);
Back to your question: why is it necessary to use reselect for this. Reselect in this case isn't providing any optimizations. So the main purpose of reselect is that it provides the ability to compose selectors together.
By using this composition feature we can make collections.ts only have selectors for the collections slice. Then in index.ts we can have selectors at the AppState level. Furthermore we can have selectors that work across different slices of the store such as this one:
/**
* Some selector functions create joins across parts of state. This selector
* composes the search result IDs to return an array of books in the store.
*/
export const getSearchResults = createSelector(getBookEntities,
getSearchBookIds, (books, searchIds) => {
return searchIds.map(id => books[id]);
});
Yes, there may be performance gains. If fromCollection.getLoading computation is expensive, reselect would avoid useless recalculations until getCollectionState keeps returning the same value.

Resources