Store: Caching / memoization strangeness with Selectors and navigation

Created on 18 Jun 2019  路  9Comments  路  Source: ngxs/store

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[x] Support request => https://github.com/ngxs/store/blob/master/CONTRIBUTING.md
[ ] Other... Please describe:

Current behavior


When navigating between pages, I am seeing out-dated state values getting returned by Select()s.

Expected behavior


I would expect a Select() to return a value appropriate to the current state.

Minimal reproduction of the problem with instructions

Stackblitz: https://stackblitz.com/edit/angular-hcamcb

  • Load the stackblitz: https://angular-hcamcb.stackblitz.io/.
  • Type 'hol' in the search box.
  • 3 records should be returned and shown.
  • Click any of the 3 records, you will be taken to a page with "Hello World".
  • Click the "Back" button in your browser.
  • Very briefly, you will see the search results appear and then disappear.

What is the motivation / use case for changing the behavior?


I would expect, since the search results are cleared in the ngOnDestroy() of main-search-box.component, that when navigating back to the search page, there would be no results to display.

Environment

There should be nothing special about the environment. This stackblitz was created a few days ago and it's using whatever the latest version of libraries that are out there. Seeing the issue in both latest FireFox and Chrome so I don't think it's a browser thing either.

Notes

Am I just dumb and this is the result of memoization? Why is it if I dispatch a new ClearMainSearch() action from the ngOnInit() of the main-search-box.component, the issue goes away? Why does running it at the beginning work but not at the end?

Most helpful comment

The issue is that you are using this.store.select(SearchState.selectMainSearchSpace). You are returning a non completing Observable, and probably creating a lot of subscriptions while doing if you keep using your app without refreshing.

Couple solutions for you:

  1. Add a take(1) pipe stackblitz
  2. use this.store.selectOnce
  3. use getState().space

Bottom line, make sure you return an observable from your action that actually completes

All 9 comments

@arturovt what do you think?

@splincode it's impossible to read that code provided in the stackblitz, it's extremely hard :confused:

@splincode it's impossible to read that code provided in the stackblitz, it's extremely hard 馃槙

Anything I can do to clear it up?

There's only 3 actions. One populates the search space, one takes a search term and populates search results (based on the search space), and the third sets the search results to null.

Just make some simpler example, without nested states :D I have a limited amount of time, so it would be great if you could do something small and easy. This is like "10 seconds question" :smiley:

Ok, here's an updated stackblitz: https://stackblitz.com/edit/angular-dvews1

(I had made some changes to the other one before I posted it here, but apparently it was in read-only mode so nothing got saved. If I can simplify things more, let me know.)

Here's what I don't understand, from my console.logs when navigating back to the original search page (after having visited the 'Hello World' page).

Beginning AssembleMainSearchSpace {term: null, results: null, space: {鈥} search.ngxs.ts:100
Selecting selectMainSearchSpace {term: null, results: null, space: {鈥} search.ngxs.ts:95
Selecting selectMainSearchSpace {term: "hol", results: Array(3), space: {鈥} search.ngxs.ts:95

selectMainSearchSpace is getting called twice (which is probably unnecessary, but it's coded to work that way for now) but it is returning different values. UpdateMainSearch is not running so it's not like it is getting changed.

The issue is that you are using this.store.select(SearchState.selectMainSearchSpace). You are returning a non completing Observable, and probably creating a lot of subscriptions while doing if you keep using your app without refreshing.

Couple solutions for you:

  1. Add a take(1) pipe stackblitz
  2. use this.store.selectOnce
  3. use getState().space

Bottom line, make sure you return an observable from your action that actually completes

Thank you Pierre, I knew that NGXS will handle subscribe / unsubscribe when you return an observable from an Action, but I thought the @ Select decorators did something similar.

@Senneseph actually ngxs does not handle subscribe/unsubscribe. If you return a non completing observable from an action handler, and use this.store.dispatch(new Action()).subscribe() it will never reach the subscribe. So, basically, never do that :)

Oh, I guess I need to re-read the docs about Async Actions

You might notice I returned the Observable and just did a tap. If we return the Observable, the framework will automatically subscribe to it for us, so we don't have to deal with that ourselves.

Was this page helpful?
0 / 5 - 0 ratings