0
votes

Hi I'm trying to understand why it gives me this infinite loop

i've tried lookin up the internet but nothing fits my need

this is my effect

    /**
     * EFFECT TO GET ALL USRS 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 }))
        })
    ))

    /**
     * FETCH GROUP PER 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 }))
        })
    ))

and this is how i dispatch

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,
        })
    }),
    on(LOAD_USERS_FAILED, (state, err) => {
        return {
            ...state,
            error: err,
            selectedUserId: undefined,
            isLoading: false,
        }
    }),
    on(LOAD_USERS, (state) => {
        return {
            ...state,
            error: undefined,
            selectedUserId: undefined,
            isLoading: true
        }
    }),
    /**
     * ==============================================
     * END OF LOADING REDUCERS
     * ==============================================
     */
    on(LOAD_USER_GROUP, (state) => {
        return {
            ...state,
            error: undefined,
            selectedUserId: undefined,
            isOtherLoading: true
        }
    }),
    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 })
    }),
)

I made sure that the effect is not calling itself to cause the infinite loop. or calling another action that will ultimately call itself.

but still it is giving me infinite loop.

UPDATE I observe if i remove this part in the reducer it is not giving me the infinite loop result but I need it to update my 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 updated the way i retrieve the user below. In just 1 compoenent

ngOnInit(): void {
    this.sub.add(
      this.store$.select(routerInfo).pipe(
        concatMap(routerValue => {
          const id = routerValue.params['id'];
          return this.store$.select(selectUserById(id)).pipe(
            tap(() => this.store$.dispatch(LOAD_USER_GROUP({ id: id }))),
            map((user: User) => {
              console.log(JSON.stringify(user))
            })
          )
        })
      ).subscribe(() => {
        this.isLoading$ = this.store$.select(isLoading)
      })
    )
  }
2
do you have redux dev tools? to see which actions are in loop? do you have more effects listening LOAD_USER_GROUP_SUCCESS ? or are you listening some selectors that trigger a change when you upate your user - user group?Manuel Panizzo
Yes I have and i can see that this LOAD_USER_GROUP_SUCCESS is in loop same with this LOAD_USER_GROUP. the only selector i use is the select all which basically retrieves all usersJayson Gonzaga
I dont see nothing wrong in your code. Maybe you can check the strings of your actions in order to be sure that those are unique. Or be sure that the only place where you have the dispatcher of "LOAD USER GROUP" is that "NgOnInit". And then put a console log inside that ngOnInit to be sure that you aren't re-rendereing that component every time that you update your store.Manuel Panizzo
@ManuelPanizzo - I updated the question.. with the latest implementation i did. but still getting the infinite loopJayson Gonzaga
how do you update the entity aside from the addOne provided by the framework?Jayson Gonzaga

2 Answers

0
votes

The reducer never dispatch action. So... addOne does not trigger any action.

Your problem is that you are getting the id from the store. with this.store$.select(selectUserById(id)) and inside that observable you are dispatching LOAD_USER_GROUP with tap(() => this.store$.dispatch(LOAD_USER_GROUP({ id: id }))), and then your Effect are listening that and the effect dispatch LOAD_USER_GROUP_SUCCESS and your reducer update the object with

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 })
    }),

that made a change to your store that emits a new value to this "this.store$.select(selectUserById(id))" and that dispatch again LOAD_USER_GROUP_SUCCESS with tap(() => this.store$.dispatch(LOAD_USER_GROUP({ id: id }))) and there is your loop.

You can avoid that loop replacing this :

  return this.store$.select(selectUserById(id)).pipe(
    tap(() => this.store$.dispatch(LOAD_USER_GROUP({ id: id }))),

with this:

      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 }))),

Im not sure if that solucion help you in your "macro logic" but with the "first" operator you only take the first value emited by the observable and you will avoid the loop.

0
votes

I was able to find a resolution. however when i click the backbutton it seems that the codes in the oninit is being called. I checked the devtools and added a logs and yes it is calling the oninit when clicking the backbutton

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('callin user')
      this.store$.dispatch(LOAD_USER_GROUP({ id: id }))
      console.log(`id ${id}`)
      this.user$ = this.store$.select(selectUserById(id))
    })
   )
  }