Unable to release memoization on ngrx selector - ngrx

I am using NGRX in an angular app and using a selector to get the current job key as follows. Using this key, an image is taken from the document store.
I was looking at some links and it is advised that ngrx is a state store, not a document store. I found that using it as a doc store slows things down immensely.
https://github.com/btroncone/ngrx-store-localstorage/issues/39
Anyway, the solution was to have a separate image store, and just handle the image key/identifier in ngrx state. Which works well.
this.store
.pipe(select(getCurrentImage))
.subscribe(async currentImageKey => {
this.loadImage(currentImageKey);//Load and set the currentFile
});
Later on in the app when the image is updated, but the image key/identified does not change, so i want to force an update by releasing the memoization from the selector. This should cause the image to be retrieved from the document store again.
This answer suggests releasing the memoization.
Create non-memoized selector on ngrx
So following that advice, have tried to release the selector before calling the image processing action.
async onAnalyseImage() {
getCurrentImage.release();
this.store.dispatch(
ImageViewerActions.analyseImage({
jobKey: this._currentFile.jobKey
})
);
}
Then once the action/effect are complete, i want to update the value of the current image with some changes however the key in the state has not changed. So i release the selector, which should set the memoization value to null.
on(ImageViewerActions.refreshImageCanvas, state => ({
...state,
currentLoadedImage: state.currentLoadedImage,
})),
The "refresh" action is called and reducer tries to set the state value to the same value in the hope that the selector will be called again.
This does not happen and memoization persists. I can change to another image, and as expected, the selector is called and updates the image.
However, i cannot get a refresh on the same value key.

The currentLoadedImage is a string.
The memoization does a string comparison and in Javascript a string is a string is a string.
So even though a new string object could be passed, it will still appear as the same object. a===b

Related

Why is my Observable emitting more values than expected, and why is auditTime a fix?

I asked a question here recently about observables and you guys were of really great help (as always). Now I'm having a similar situation, and me and my team-mate are bending our brains over it.
The bug to fix was: user sees a collection of assets, and on browser refresh the wrong set of assets was being loaded. It turns out the key to the problem was one particular pipe observing the currently selected collection. Here's the relevant code:
this.selectedCollection.pipe(
filter((v) => !!v)).subscribe((v) => {
console.log('PIPE: selected collection', v.collectionId);
this.store.dispatch(
// action jackson on redux
)
);
});
The action to be dispatched here is for loading the assets of the collection. One collection was always loaded first as default and it was conflicting with further selections made by the user.
I've also added console.logs on the relevant reducers and effect to visualize behavior.
What happens on browser refresh is this:
Collection 9-em... is the default collection we don't want to see, and collection 9uem... is the user's choice whose asset's we want to see.
The first five lines show the expected output of the observable:
default collection set as selected collection
reducer 'is loading' assets
the user triggers a change selected collection action
the selected collection value is being updated and emitted accordingly
Now we would have expected the effect to load the assets and that's it. But what happens is that the pipe keeps emitting the same values once again, which is weird, because I'm 100% sure no further value is being set from anywhere. But it would also be fine, since we end up with the desired value. Yet strangely, the reducer is handling the load actions in reverse order, which led to the wrong assets being loaded (this could be a whole different issue on top).
Adding auditTime(200) as first operator to the pipe above fixed the issue. No further values were emitted.
Now, my questions are:
Why are the values emitted twice? Could it be an inappropriate operator/subscription some place else (didn't see anything suspicious)?
And why is auditTime(200) magically fixing this?
The effect also works as a pipe of actions being filtered, and it contains an auditTime(200) operator before executing, so that it executes only on the last action. While I do understand on principle what it does, I'm not quite sure if using auditTime like that just because it works is such a good idea.
I assume this is an issue out of noob confusion resulting in using rxjs not the right way. Unfortunately, I couldn't find anything useful on google. I really don't like 'fixing' a bug by adding a line of code that I just don't understand.
Thank you so much in advance!
As requested by fridoo, here's the code for this.selectedCollection:
get selectedCollection(): Observable<collectionState.CollectionsData> {
return this.store
.select(collectionState.getSelectedCollection)
.pipe(distinctUntilChanged());
}
And for getSelectedCollection:
export const getSelectedCollection: (state: any) => CollectionsData = (state: any) =>
getCollectionsState(state)
? getCollectionsState(state).selectedCollection
: undefined;
The rest is pretty forward just objects of state, the observable created via the select method. We're not using any library for redux (not my decision), so select is implemented like this:
select<T>(fn: (state: any) => T): Observable<T> {
return this.state$.pipe(map(fn), distinctUntilChanged());
}
Does this help any further?

Modify selector in redux saga without mutating state

When using a selector, I thought that I could do whatever I wanted with the variable without modifying the state, so I was surprised that the state became mutated.
So if this is wrong (in a redux saga):
const filters = yield select(state => state.filters.filters);
filters.terms['helloo'] = "mutated";
//send data with request
yield put(requestData(filters)
How come that first line is a direct reference to the state?
Anyway, if I try using Object.assign, it also mutates state:
const filters = Object.assign({}, yield select(state => state.filters.filters));
filters.terms['helloo'] = "mutated";
How do I create a selection that is a copy of the state?
There's truly no "magic" involved here. Redux's getState() is literally just return state, and both hand-written selectors and Reselect return whatever you have written the functions to return. So, in that example, filters is the actual object reference that's nested inside the store state, because that's what your function returned.
Per the Redux docs page on "Immutable Update Patterns", you need to copy all levels of nesting that you want to update. In your example, you're making a copy of filters, but not filters.terms, so terms is also still the original object that's in the store. You would need to make a copy of that as well, and modify the copy.

What prevents code from changing the store state?

The store has a method called getState that will return the current state of the store.
What prevents code somewhere in my application from (accidentally) modifying the returned state from store?
Let's say i call this:
let state = store.getState();
state.someProperty = 'fun';
The implementation that i've found on getState on the store object simply returns the inner state object that gets overwritten with each new action.
const getState = () => state;
In between actions/new states what prevents code from modifying the state that will be read by another subscriber? In my above example, setting someProperty to 'fun' will persist inside the store on the state property, until overwritten.
While i'm obviously not supposed to modify the state, a simple mistake might bind the state to some component that (unknowingly) modifies its inputs - perhaps on a 2-way binding in an angular environment?
<app-some-component [user]="state"></app-some-component>
Shouldn't getState() be implemented as a clone of its state model?
P.S. This is not specifically related to Angular - which is why i didn't add the tag - to allow more people not used to Angular to answer the question.
The answer is: nothing :)
The core Redux library itself technically doesn't actually care if state gets mutated or not. You could actually mutate in your reducers, or have other parts of your app get the state tree and mutate it, and the store itself wouldn't know or care.
However, mutation will break time-travel debugging, as well as make tests unreliable. Even more importantly, the React-Redux library assumes that you will handle your state immutably, and relies on shallow equality comparisons to see if the state has changed. (This is the reason why "Why isn't my component re-rendering?" is in the Redux FAQ. 99.9% of the time, it's due to accidental mutation.)
If you are concerned about mutation, you can use a library like Immutable.js instead of plain JS objects, or use one of the several tools for freezing your state in development to catch mutations.

Component is not unmount after its delete in store

Project (Todolist) was created with immutable library, source here
Store structure: project have many tasks, In redux store: State - map, projects, tasks - Records
When I asyncly remove project ...
export const removeProject = project => (dispatch) => {
if (!isProjectExist(project)) return Promise.resolve()
return projectService
.delete(project)
.then(
() => {
dispatch(remove(project))
console.log("post removeProject resolved")
},
handleError,
)
}
.... that was created after initialization - it will be deleted and properly unmounted, but when project was passed as initialState - ProjectList will not be rerendered, and ProjectItem try to render itself with stale data, and fail, as in picture
It have tests
It looks like reducer returs changed data, but I use immutablejs, and previously i use normalizr-immutable, but I thought that source of issue in this library and write my own normalizeInitialState (source), it did not help, now I think that maybe source of problem in redux-immutable
I struggled entire day on solving of this problem
creator of redux says
I don't think this is something we can fix. React state changes are
asynchronous and React may (or may not) batch them. Therefore, the
moment you press “Remove”, the Redux store updates, and both Item and
App receive the new state. Even if the App state change results in
unmounting of Items, that will happen later than mapStateToProps is
called for Item.
Unless I'm mistaken, there is nothing we can do. You have two options:
Request all required state at App (or a lower, e.g. ItemList) level
and pass it down to “dumb” Items. Add safeguards to mapStateToProps
for “currently unmounting” state. For example, you may return null
from render in this case. Potentially we could have the component
generated by connect() return null from its render if mapStateToProps
returned null. Does this make any sense? Is this too surprising?
Hm, I never saw stubs like return (<div></div>) or safeguards in mapStateToProps in others code
markerikson
I'm not entirely sure I follow what exactly your problem is, but as a
guess: it sounds like the child component is re-rendering before the
parent is. This is a known issue with React-Redux v4 and earlier. The
v5 beta fixes that issue. Try installing react-redux#next and see if
that takes care of your problem.

How to clear State in the #ngrx?

I am building an mobile app using Ngrx and Angular2. I would like to clear the Store when user logout from my application? Can anyone know how to do this?
You should have an clear action in each of your reducer, which will clean respective properties in the store. Dispatch clear actions in each of the reducer manually when you call logout. I am not sure right now if its there an option to clean the entire store in one go.
Alternative:
A more cleaner and faster approach would be. Whenever you call an action via store.dispatch it calls all your reducers with that action.type. Say your action type name is CLEAR, put this action in each of your reducer to clear the respective property of the store. And call store.dispatch with action.type = 'CLEAR' ONCE, it will clear all the properties of the store.
If it confuses let me know, I will try to explain with code.
The solution is to write the root reducer.
It's similar to this:
export function reducer(state: any, action: any): ActionReducer<any> {
if (action.type === 'CLEAR STATE') {
state = undefined;
}
return appReducer(state, action);
}
Check this: How to reset the state of a Redux store?
My guess as to a way to do this would be to have a component that has the store registered on it. Put it in a router-outlet or a structural directive that could force a destroy and init on the component when the value changes.
Since the store is registered as a provider for the component (unless I'm mistaken) and should be tied to it, it should be destroyed when the component is destroyed and a new one created with a new component. Just a thought. I haven't had time to test it.
Don't register the provider in a parent component though. I believe the injectables work through a hierarchy.

Resources