Ionic infiniteScroll: TypeError: Cannot read property '0' of undefined

Viewed 1211

I'm trying to display all items in a category. I have the slug or the keyword of a category, I can use that to identify the parent category, I get the parent category and use that to search for all categories having similar parent. I have two pagination related errors, the errors are related.

The url ads_url: https://jokerleb.com/wp-json/wp/v2/ads?per_page=100&&page=. For example page=1

service

  getAds(page): Observable<any[]> {
    return this.http.get(this.api_url + page)
      .flatMap((ads: any[]) => {
        if (ads.length > 0) {
          return Observable.forkJoin(
            ads.map((ad: any) => {
              return this.http.get(this.ads_thumb_url + ad.id)
                .map((res: any) => {
                  let media: any = res;
                  ad.media = media;
                  return ad;
                });
            })
          );
        }
        return Observable.of([]);
      });
  }

ts

  constructor(public navCtrl: NavController, public navParams: NavParams, public renderer: Renderer, public zone: NgZone, public adsProvider: AdsProvider) {

    this.category = this.navParams.get('category');

    this.loadAds();
  }
  loadAds(infiniteScroll?) {
    this.adsProvider.getAds(this.page).subscribe((data: any) => {
      if (!this.category.main) {
        if (this.category.slug) {
          for (let i = 0; i < data.length; i++) {
            if (data[i] !== undefined) {
              if (data[i].pure_taxonomies.ad_cat[0].slug.trim().toLowerCase() === this.category.slug.trim().toLowerCase()) {
                this.item_category = data[i].pure_taxonomies.ad_cat[0].term_id;
                break;
              }

            }
          }
          if (this.item_category !== undefined) {
            for (let i = 0; i < data.length; i++) {
              if (data[i].pure_taxonomies !== undefined) {
                if (data[i].pure_taxonomies.ad_cat[0].term_id === this.item_category ||
                  data[i].pure_taxonomies.ad_cat[0].slug.trim().toLowerCase() === this.category.slug.trim().toLowerCase()) {
                  this.items.push(data[i]);
                }
              }
            }
          }
        }
      } else {
        if (this.category.main === 2) {
          for (let i = 0; i < data.length; i++) {
            if (data[i] !== undefined) {
              if (data[i].pure_taxonomies.ad_cat[0].slug.trim().toLowerCase() === this.category.slug.trim().toLowerCase()) {
                this.item_category = data[i].pure_taxonomies.ad_cat[0].parent;
                break;
              }

            }
          }
          if (this.item_category !== undefined) {
            for (let i = 0; i < data.length; i++) {
              if (data[i].pure_taxonomies !== undefined) {
                if (data[i].pure_taxonomies.ad_cat[0].parent === this.item_category) {
                  this.items.push(data[i]);
                }
              }
            }
          }
        }
      }
      if (infiniteScroll) {
        infiniteScroll.complete();
      }
    });
  }
  loadMore(infiniteScroll) {
    this.page++;
    this.loadAds(infiniteScroll);
  }

html

<ion-content fullscreen class="home-content" [ngSwitch]="tab">
  <div *ngIf="items.length>0; then thenTemplateName else elseTemplateName">
  </div>
  <ng-template #thenTemplateName>
    <div class="all-lists">
      <div class="all-category" *ngSwitchCase="1">
        <ion-card class="category-card" *ngFor="let item of items" (click)="onShowItemDetail(item);">
          <span *ngIf="item.media.length">
            <img src="{{item.media[item.media.length-1].media_details.sizes.medium.source_url}}" />
          </span>
          <div class="card-title" text-uppercase>
            <h4 color="secondary">{{item?.title.rendered}}</h4>
          </div>
        </ion-card>
      </div>
    </div>
    <ion-infinite-scroll (ionInfinite)="loadMore($event)" loadingSpinner="bubbles">
      <ion-infinite-scroll-content></ion-infinite-scroll-content>
    </ion-infinite-scroll>
  </ng-template>
  <ng-template #elseTemplateName>
    <div class="all-lists">
      <div class="all-category" *ngSwitchCase="1">
        <ion-card text-center>
          <ion-card-header>
            This Category is empty
          </ion-card-header>
          <ion-card-content>
            This Category is empty
          </ion-card-content>
        </ion-card>
      </div>
    </div>
  </ng-template>
</ion-content>

Sometimes I get TypeError: Cannot read property '0' of undefined, if item not found, I don't get the error, the error happens if I scroll down too much, loadMore() function gets called, it finds nothing and I get the error.

I might have two problems in my code, due to loadMore and pagination, the first one being the error above and the second one is that sometimes the last 2 or 3 items don't show. Perhaps I'm increasing the page without checking if there are still items on to be displayed. I don't know how to fix that.

I think if there's a way to tell whether items exist before paginating, both errors will be fixed.

3 Answers

As you already mentioned in the question, this happens when you try to access elements when they are empty. You can handle with a if check wherever you are trying to access items as follows,

if(data && data.length > 0){
  for (let i = 0; i < data.length; i++) {
}

Try this (I added more checks)

loadAds(infiniteScroll?) {
    this.adsProvider.getAds(this.page).subscribe((data: any) => {
      if (!this.category.main) {
        if (this.category.slug) {
          for (let i = 0; i < data.length; i++) {
            if (data[i] !== undefined && (data[i].pure_taxonomies.ad_cat || []).length ) {
              if (data[i].pure_taxonomies.ad_cat[0].slug.trim().toLowerCase() === this.category.slug.trim().toLowerCase()) {
                this.item_category = data[i].pure_taxonomies.ad_cat[0].term_id;

                break;
              }

            }
          }
          if (this.item_category !== undefined) {
            for (let i = 0; i < data.length; i++) {
              if (data[i].pure_taxonomies !== undefined && (data[i].pure_taxonomies.ad_cat || []).length) {
                if (data[i].pure_taxonomies.ad_cat[0].term_id === this.item_category ||
                  data[i].pure_taxonomies.ad_cat[0].slug.trim().toLowerCase() === this.category.slug.trim().toLowerCase()) {

                  this.items.push(data[i]);
                }
              }
            }
          }
        }
      } else {
        if (this.category.main === 2) {
          for (let i = 0; i < data.length; i++) {
            if (data[i] !== undefined && data[i].pure_taxonomies && (data[i].pure_taxonomies.ad_cat || []).length) {
              if (data[i].pure_taxonomies.ad_cat[0].slug.trim().toLowerCase() === this.category.slug.trim().toLowerCase()) {
                this.item_category = data[i].pure_taxonomies.ad_cat[0].parent;

                break;
              }

            }
          }
          if (this.item_category !== undefined) {
            for (let i = 0; i < data.length; i++) {
              if (data[i].pure_taxonomies !== undefined && (data[i].pure_taxonomies.ad_cat || []).length) {
                if (data[i].pure_taxonomies.ad_cat[0].parent === this.item_category) {

                  this.items.push(data[i]);
                }
              }
            }
          }
        }
      }
      if (infiniteScroll) {
        infiniteScroll.complete();
      }
    });
  }

(I'm changing my answer to letting you know the problem with the "missing items".)

From your comment:

please clone social-login-untested branch, deploy it, go to ford, for example you'll see that only 1 car shows

And your question:

I might have two problems in my code, due to loadMore and pagination, the first one being the error above and the second one is that sometimes the last 2 or 3 items don't show.

So I cloned the social-login-untested branch of your GitHub repo, and tested the app locally.

I went to Vehicles → Cars → Ford, and noticed that there's only one item showing, which is actually as expected since on the first page (items 1st to 100th), there's only one "Ford" item, and the rest are on pages #2, etc.

Which means, the rest of the "Ford" items are only visible when you scroll down on the page — and only if you can scroll down the page.. i.e. the document's height is greater than the window's height.

Otherwise, you're stuck on page #1's results, and pages #2 and other pages would never be loaded. On Chrome, toggle the device toolbar → choose "Responsive" and try 400 x 320 px, like below:

At 400 x 320 px, you can scroll down, so all the "Ford" items would be loaded.

One way I could think of how you could fix the issue, is with a logic that looks something like this (see the CategoryDetailsPage.loadAds() in src/pages/category-details/category-details.ts):

if ( {DOCUMENT'S HEIGHT} <= {WINDOW'S HEIGHT} ) {
  // Loads next page without waiting for the scrolling to occur; but stops
  // loading when scrolling starts - for that, the logic is not included.
  this.loadMore( infiniteScroll );
} else if ( infiniteScroll ) {
  infiniteScroll.complete();
}

.. but (since I'm actually less experienced in AngularJS and Ionic) you would need to create that on your own.. You may also want to add a listener to the window/document onscroll event and stop the loadMore() on page scroll. And make sure there are no (or just remove/hide) duplicate items.

Nonetheless, I hope this "new" answer will help you. =)

Additional Note

As I pointed in my comment to your question, if necessary, you can retrieve the total available number of pages via the header X-WP-TotalPages. See https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/

Related