Issue with ngRX infinite loop caused by the updateOne function in the adapter

Hey there, I'm struggling to figure out why my code is stuck in an infinite loop.

I've searched online extensively but haven't found a solution that fits my specific issue.

This is the code snippet causing the problem:

    /**
     * CODE SNIPPET TO RETRIEVE USERS FROM THE KEYCLOAK SERVER
     */
    loadUsers$ = createEffect(() => this.action$.pipe(
        ofType(LOAD_USERS),
        switchMap(() => {
            return this.userService.fetchAll().pipe(
                map((data: T[]) => LOAD_USERS_SUCCESS({ list: data }))
            )
        }), catchError((err) => {
            return of(LOAD_USERS_FAILED({ error: err }))
        })
    ))

    /**
     * FETCHING GROUP FOR EACH USER
     */
    loadUserGroup$ = createEffect(() => this.action$.pipe(
        ofType(LOAD_USER_GROUP),
        switchMap((action) => {
            return this.userService.fetchGroupUser(action.id).pipe(
                map((data: any[]) => LOAD_USER_GROUP_SUCCESS({ id: action.id, list: data }))
            )
        }), catchError((err) => {
            return of(LOAD_USER_GROUP_FAILED({ error: err }))
        })
    ))

Below are the dispatch methods used:

sub-component.ts

  ngOnInit(): void {
    console.log(`user id ${this.userId}`)
    this.store$.dispatch(LOAD_USER_GROUP({ id: this.userId }))
  }

parent.ts

users$: Observable<User[]> = this.store$.select(selectAll)
  isLoading$: Observable<boolean> = this.store$.select(isLoading)
  isLoadingOther$: Observable<boolean> = this.store$.select(isLoadingOther)

  constructor(private store$: Store<any>) {
    this.store$.dispatch(LOAD_USERS())
  }

Reducer

export const userReducer = createReducer(
    initialState,

    // Loading Reducers
    on(LOAD_USERS_SUCCESS, (state, { list }) => {
        return adapter.addAll(list, {
            ...state,
            selectedUserId: undefined,
            isLoading: false,
        })
    }),
    // More reducers...
)

I have verified that there are no recursive calls causing the infinite loop. However, the issue persists.

UPDATE: After removing a specific section from the reducer, the infinite loop stops. Yet, that section is necessary for updating the selected entity.

on(LOAD_USER_GROUP_SUCCESS, (state, { id, list }) => {
        return adapter.updateOne({
            id: id,
            changes: { ...state.entities[id], group: list }
        }, { ...state, isLoading: false, isOtherLoading: false, error: undefined })
    }),

UPDATED: I have revised how I retrieve the user in just one component.

ngOnInit(): void {
    // Code snippet here ...
  }

Answer №1

The reducer isn't dispatching any actions, so the addOne function isn't triggering anything.

The issue lies in fetching the id from the store using

this.store$.select(selectUserById(id))
. Within that observable, you are dispatching LOAD_USER_GROUP with
tap(() => this.store$.dispatch(LOAD_USER_GROUP({ id: id }))),
. Subsequently, your Effect is listening to that and dispatching LOAD_USER_GROUP_SUCCESS, causing the reducer to update the object as follows:

on(LOAD_USER_GROUP_SUCCESS, (state, { id, list }) => {
        return adapter.updateOne({
            id: id,
            changes: { ...state.entities[id], group: list }
        }, { ...state, isLoading: false, isOtherLoading: false, error: undefined })
    }),

This change in the store triggers a new value emission to

this.store$.select(selectUserById(id))
, leading to another dispatch of LOAD_USER_GROUP_SUCCESS with
tap(() => this.store$.dispatch(LOAD_USER_GROUP({ id: id })))
, thus creating a loop.

To prevent this loop, you can replace the previous code snippet with the following:

      return this.store$.select(selectUserById(id)).pipe(
        first(), // import this from "rxjs/operators" I'm not sure about the route but I think that is it
        tap(() => this.store$.dispatch(LOAD_USER_GROUP({ id: id }))),

I cannot guarantee if this solution will address your "macro logic," but using the "first" operator ensures only the initial value emitted by the observable is taken, effectively avoiding the loop.

Answer №2

I managed to come up with a solution, but I noticed that when I click the back button, the code in the onInit function is being executed again. After checking in the devtools and adding some logs, it confirmed that the onInit function is triggered when clicking the back button.

constructor(private store$: Store<any>, private router: Router) {
    this.store$.dispatch(LOAD_USERS())
  }

  ngOnDestroy(): void {
    this.sub.unsubscribe()
  }

  ngOnInit(): void {
   this.sub.add(
    this.store$.select(routerInfo).pipe(
      map(val => val.params['id'])
    ).subscribe(id => {
      console.log('Calling user')
      this.store$.dispatch(LOAD_USER_GROUP({ id: id }))
      console.log(`ID: ${id}`)
      this.user$ = this.store$.select(selectUserById(id))
    })
   )
  }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When using Sequelize, you may notice that extra spaces are automatically added at the end of the DataTypes.CHAR data type

Here is an example of how I structure my Store.ts file: import {DataTypes, Model, ModelAttributes} from "sequelize"; export default class Store extends Model { declare id: number declare name: string declare phone: string } export const S ...

Divide the string into several segments according to its position value

Here is a piece of text that I would like to divide into multiple sections, determined by the offset and length. If you have any questions or comments and would like to get in touch with ABC, please go to our customer support page. Below is a function ...

Utilize the ngClass directive in conjunction with ngFor loop functionality

Currently, I am working on rendering a list of elements using the *ngFor directive in Angular. However, I have encountered an issue where only certain parts of the text within the list items should be bold based on specified requirements. I attempted to ac ...

Ways to ensure that your Angular component.ts file is executed only after the body has completely loaded without relying on any external

I am attempting to add an event listener to an element created with a unique identifier using uuid, but I keep getting an error that states "Cannot read properties of null (reading 'addEventListener')" export class CommentItemComponent implements ...

What is the most graceful method to define a class attribute just once?

Is there a clean and efficient way to set a value only once within a TypeScript class? In other words, is there a way to make a value read-only after it has been assigned? For instance: class FooExample { public fixedValue: string; public setFixe ...

The issue lies with the Cookies.get function, as the Typescript narrowing feature does not

Struggling with types in TypeScript while trying to parse a cookie item using js-cookie: // the item 'number' contains a javascript number (ex:5) let n:number if(typeof Cookies.get('number')!== 'undefined'){ n = JSON.pars ...

TSX is throwing an error: "No matching overload found for this call."

Just starting out with React and TypeScript here! I tried passing the propTypes into a styled-component and ran into this error message: Oh no, there's an issue with the overloads. Overload 1 of 2 seems to be missing some properties. Overload 2 of ...

Error TS2694 is being caused by Electron Typescript Material-UI withStyles because the namespace "".../node_modules/csstype/index"" does not have an exported member called 'FontFace'

While I am experienced with Material-UI, I am relatively new to Electron and using React, TypeScript, and Material-UI together. Recently, I encountered an error while attempting to create an electron boilerplate code for future project initialization. Init ...

Guide to adding a CSS class to an Ionic2 toast

i found a helpful resource on applying cssClass to my toast component. within my HTML, I have buttons: <button ion-button (click)="presentToast()"> toast</button> Here is the code snippet from my .ts file: presentToast() { let toast = t ...

Sharing an angular app URL containing query parameters with multiple users

I am in need of a feature that allows me to transfer the filter settings on a page to another user. For instance, if I apply certain filters on a specific page, I would like to share the URL with those filters already applied to other users. ...

Angular HTTP post is failing on the first attempt but succeeds on the second try

Just started working on my first Angular exercise and encountered an issue where I am receiving an undefined value on the first attempt from an HTTP post request. However, on the second try, I am getting the proper response in Edge and Firefox. Thank you f ...

What is the best way to add multiple elements to an array simultaneously?

I am facing an issue with my array arrayPath. I am pushing multiple values into it, but there are duplicates in the data. When the value of singleFile.originalFilename is a duplicate, I do not want to push that duplicate value into arrayPath. How can I ach ...

Having issues with ngbDropdown in Angular 4 from ng-bootstrap?

My dropdown menus are malfunctioning. I attempted to follow advice from this source, where I upgraded to bootstrap 4-alpha, but unfortunately, the issue persists. Here is an excerpt from my package.json file: "@angular/animations": "^4.3.0", ... // ...

Declaring Objects and Relationships in Angular Models

Wondering if it's possible to declare an object inside my model. First attempt: export class Employee{ emp_id: number; emp_fname: string; emp_lname: string; emp_birth: string; emp_status: string; emp_photo: string; emp_dep ...

What is the best method to trigger a bootstrap modal window from a separate component in Angular 8?

I have successfully implemented a bootstrap modal window that opens on a button click. However, I am now facing difficulty in opening the same modal window from a different component. Below is the code I have tried: <section> <button type=&quo ...

SVG: organizing objects based on event priority

I am struggling with layering and event handling in an SVG element. Check out the example here: https://stackblitz.com/edit/angular-ivy-rkxuic?file=src/app/app.component.ts app.component.ts import { Component, VERSION } from '@angular/core'; @ ...

Angular 6 Material now allows for the selection of a mat-tab-link by displaying an underlining bar

My website features a mat-tab-nav-bar navigation bar, but I'm facing an issue with the mat-tab-link blue underlining bar. It doesn't move to highlight the active button, instead, it stays on the first button. However, the buttons do change into t ...

The propagation of onClick events in elements that overlap

Having two divs absolutely positioned overlapping, each containing an onClick handler. The issue is that only the top element's onClick handler fires when clicked. React 17 is being used. Here is some sample code: <div style={{ position: "abs ...

How to modify attributes using ng-content in Angular 2

Looking for a way to modify the attribute of the top div within the ng-content in my code. Here's an example snippet: <ng-container> <ng-content select="[content-body]"></ng-content> </ng-container> For instance, I want t ...

The @angular/fire package is unable to locate the AngularFireModule and AngularFireDatabaseModule modules

I am facing some challenges while trying to integrate Firebase Realtime Database into my Angular project. Specifically, I am encountering difficulties at the initial step of importing AngularFireModule and AngularFireDatabaseModule. To be more specific, I ...