Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add the possibility to specify action types to invoke the reducer in the storage sync helper #1320

Open
wants to merge 2 commits into
base: release/9.6
Choose a base branch
from

Conversation

Abhirocks889
Copy link
Contributor

Proposed change

This PR adds a new optional property actionTypes to the SyncStorageConfig that will specify the action types for which the reducer should be invoked in case the state is undefined. This is required for syncing feature stores for which the action type UPDATE needs to be taken into account.

Related issues

@Abhirocks889 Abhirocks889 requested a review from a team as a code owner February 6, 2024 08:22
@Abhirocks889
Copy link
Contributor Author

Essentially, when a store is imported inside a lazy loaded module and the store sync meta reducer is used, the state is undefined as can be seen in the attached screenshot:

Since the action type INIT is specified, the reducer is not invoked resulting in an empty state.

So, for such scenarios we need to specify the UPDATE action type as well for invoking the reducer.

We do not have the issue when the sync is done in the root app module.

image

@kpanot
Copy link
Contributor

kpanot commented Feb 9, 2024

Essentially, when a store is imported inside a lazy loaded module and the store sync meta reducer is used, the state is undefined as can be seen in the attached screenshot:

Since the action type INIT is specified, the reducer is not invoked resulting in an empty state.

So, for such scenarios we need to specify the UPDATE action type as well for invoking the reducer.

We do not have the issue when the sync is done in the root app module.

image

Thank you for the explanation.
So would not make more sense to run the reducer on init or change if init has not be trigger for the store previously?

@Abhirocks889
Copy link
Contributor Author

Essentially, when a store is imported inside a lazy loaded module and the store sync meta reducer is used, the state is undefined as can be seen in the attached screenshot:
Since the action type INIT is specified, the reducer is not invoked resulting in an empty state.
So, for such scenarios we need to specify the UPDATE action type as well for invoking the reducer.
We do not have the issue when the sync is done in the root app module.
image

Thank you for the explanation. So would not make more sense to run the reducer on init or change if init has not be trigger for the store previously?

Indeed, for now it is INIT and UPDATE for which we need to invoke the reducer. However, there could possibly be more in the future. Hence, I thought of keeping it configurable to specify the action types from the application level instead of repeatedly making changes here whenever a new action is identified.

@kpanot
Copy link
Contributor

kpanot commented Feb 12, 2024

Essentially, when a store is imported inside a lazy loaded module and the store sync meta reducer is used, the state is undefined as can be seen in the attached screenshot:
Since the action type INIT is specified, the reducer is not invoked resulting in an empty state.
So, for such scenarios we need to specify the UPDATE action type as well for invoking the reducer.
We do not have the issue when the sync is done in the root app module.
image

Thank you for the explanation. So would not make more sense to run the reducer on init or change if init has not be trigger for the store previously?

Indeed, for now it is INIT and UPDATE for which we need to invoke the reducer. However, there could possibly be more in the future. Hence, I thought of keeping it configurable to specify the action types from the application level instead of repeatedly making changes here whenever a new action is identified.

I don't really see cases where you will need more than these events.
I would suggest to do the suggested change (fallback to change event of no init has been called) instead of exposing this mechanism

@Abhirocks889
Copy link
Contributor Author

Abhirocks889 commented Feb 12, 2024

Essentially, when a store is imported inside a lazy loaded module and the store sync meta reducer is used, the state is undefined as can be seen in the attached screenshot:
Since the action type INIT is specified, the reducer is not invoked resulting in an empty state.
So, for such scenarios we need to specify the UPDATE action type as well for invoking the reducer.
We do not have the issue when the sync is done in the root app module.
image

Thank you for the explanation. So would not make more sense to run the reducer on init or change if init has not be trigger for the store previously?

Indeed, for now it is INIT and UPDATE for which we need to invoke the reducer. However, there could possibly be more in the future. Hence, I thought of keeping it configurable to specify the action types from the application level instead of repeatedly making changes here whenever a new action is identified.

I don't really see cases where you will need more than these events. I would suggest to do the suggested change (fallback to change event of no init has been called) instead of exposing this mechanism

Well, I found one where the store dev tools recompute action is accompanied by an undefined state:

image

Granted, it can be avoided in prod by disabling dev tools but on dev, it breaks the application.

@@ -328,7 +331,7 @@ export const syncStorage = (config: SyncStorageConfig) => (reducer: any) => {

// If state arrives undefined, we need to let it through the supplied reducer
// in order to get a complete state as defined by user
if (action.type === INIT && !state) {
if ((action.type === INIT || action.type === UPDATE || action.type === RECOMPUTE) && !state) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UPDATE and RECOMPUTE should trigger the reducer only if no INIT has been emitted before for this store, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and when syncing the state for feature stores, INIT is not dispatched. Only UPDATE and RECOMPUTE (in dev mode)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turned this comment as unresolved, please do not resolve a question without the question author approval

If we agree on the fact that the reducer should be called on RECOMPUTE and INIT actions only if the INIT event has not be emitted for this store, this is not the implementation on the code.
So:

  • either you should consider checking that the INIT action has not been emitted to this store before checking for the 2 others actions,
  • either it s a case it never happen (then an explanation as response of the question would be appreciated),
  • either executing the reducer a second time on a store that has already got an INIT action is not an issue (an explanation would also be needed).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, when syncing the state using the syncStorage method within a lazily loaded feature module, the INIT action is not received and therefore, the reducer is not invoked resulting in an undefined state.

So, point no 2 from your comment. INIT is never called.

Please check the minimal reproduction of the same: https://github.com/Abhirocks889/store-sync-error

@@ -3,6 +3,9 @@ import { deepFill } from '@o3r/core';
import type { Logger } from '@o3r/core';
import type { StorageKeyConfiguration, StorageKeys, SyncStorageConfig, SyncStorageSyncOptions } from './interfaces';

/** The recompute action type of ngrx store devtools */
const RECOMPUTE = '@ngrx/store-devtools/recompute';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could use RECOMPUTE from @ngrx/store-devtools instead of redefining the variable

Copy link
Contributor Author

@Abhirocks889 Abhirocks889 Feb 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would add the entire store dev tools chunk to the main bundle if we use this method for syncing stores in the app module.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A compromise can be to import the type only (to ensure alignment):

import type { RECOMPUTE } from '@ngrx/store-devtools';

const RECOMPUTE: typeof RECOMPUTE = '@ngrx/store-devtools/recompute';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@Abhirocks889
Copy link
Contributor Author

Also, @kpanot , @matthieu-crouzet I have created a repo for minimal reproduction of the issue:
https://github.com/Abhirocks889/store-sync-error
You can run the app to reproduce the same issue. Check the readme for more details

@@ -328,7 +332,7 @@ export const syncStorage = (config: SyncStorageConfig) => (reducer: any) => {

// If state arrives undefined, we need to let it through the supplied reducer
// in order to get a complete state as defined by user
if (action.type === INIT && !state) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should check syncForFeature when action.type is not INIT

I don't understand why we need RECOMPUTE
because UPDATE will be called when loading the lazy loaded store, and it should be enough no ?
RECOMPUTE seems to be for devtools so in production mode it will not be called no ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the check. Also, for the second question, we need RECOMPUTE because in dev mode, we get an undefined state for both UPDATE and RECOMPUTE action types even if the reducer is invoked for UPDATE, the state arrives as undefined for RECOMPUTE. Hence, the app breaks and is not usable.

Please check the reproduction: https://github.com/Abhirocks889/store-sync-error

@matthieu-crouzet
Copy link
Contributor

It seems that your issue could be fixed by using StoreSync from @o3r/store-sync
Abhirocks889/store-sync-error#1

@kpanot
Copy link
Contributor

kpanot commented Apr 10, 2024

@Abhirocks889 , is this PR still required or did the fix mentioned by @matthieu-crouzet fix your issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature]: Add the possibility to specify action types to invoke the reducer in the storage sync helper
3 participants