ionic-framework: bug: @ionic/react: ion-slides: Layout breaks when adding slides dynamically

Bug Report

Ionic version:

[cli] 5.2.2 [@ionic/react] 0.0.6

Current behavior:

When slides are being added dynamically, the layout of the slides component breaks and newly added slides sit outside of the swiper-wrapper. Issue persists even after calling component’s update() method.

Expected behavior:

When adding additional slides, slides component should be updated to properly display those added slides.

Steps to reproduce:

  1. Start a new React blank project:
    ionic start slides blank --type=react

  2. Use the following Home.tsx template:

import {IonContent, IonHeader, IonTitle, IonToolbar, IonSlides, IonSlide, IonButton} from '@ionic/react';
import React from 'react';

const slideOpts = {
    initialSlide: 0,
    speed: 400
};

class Home extends React.Component<any, any> {

    private _slidesRef = React.createRef<any>();

    constructor(props: any) {
        super(props);
        this.state = {
            slides: [
                '1 Slide',
                '2 Slide',
            ]
        };
    }

    onSlidesAdd() {
        this.setState({
            slides: [
                '1 Slide',
                '2 Slide',

                '3 Slide',
                '4 Slide',
                '5 Slide'
            ]
        }, async () => {
            await this._slidesRef.current.update();
            console.log('IonSlides updated, but issue persists');
        });
    }

    render() {
        const {slides} = this.state;

        return (
            <>
                <IonHeader>
                    <IonToolbar>
                        <IonTitle>Ionic Blank</IonTitle>
                    </IonToolbar>
                </IonHeader>
                <IonContent>

                    <IonButton onClick={() => this.onSlidesAdd()}>
                        Add slides
                    </IonButton>

                    <IonSlides ref={this._slidesRef} pager={true} options={slideOpts}>
                        {slides.map((slide: any) => (
                            <IonSlide key={parseInt(slide)}>
                                <h1>{slide}</h1>
                            </IonSlide>
                        ))}
                    </IonSlides>
                </IonContent>
            </>
        );
    }
}

export default Home;
  1. Run ionic serve
  2. Click on the “Add slides” button.

Related code:

https://github.com/m-spyratos/-ionic-react-ion-slides-adding

Other information:

Ionic info:

Ionic:

   Ionic CLI: 5.2.2 (/usr/local/lib/node_modules/ionic)
   Ionic Framework: @ionic/react 0.0.6

Utility:

   cordova-res: not installed
   native-run: not installed

System:

   NodeJS: v10.16.0 (/usr/local/bin/node)
   npm: 6.10.0
   OS: macOS Mojave

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 11
  • Comments: 34 (1 by maintainers)

Most upvoted comments

I faced this probelm also but I get to fix it.

I think the IonSlides did not re-initialize when prop [slides] updated from empty array.

The possible solution will be

{ slides.length > 0 &&
        <IonSlides ref={this._slidesRef} pager={true} options={slideOpts}>
               {slides.map((slide: any) => (
                   <IonSlide key={parseInt(slide)}>
                       <h1>{slide}</h1>
                   </IonSlide>
               ))}
          </IonSlides>
}
 <IonSlides options={slideOptsOne} **key={Object.keys( page708).length > 0 ? Object.keys((page708 as any).content_json).map(slide => slide).join('_'):1}**   >
 
`</IonSlides>`  

in my case break the ionslides ui but i’ll add the key in ionslides they working fine

one hack, that i dont like, that worked for me was setting a key value to

<IonSlides key={lengthOfArray + 'someUniqueString'}>

so anytime a new slidewas added, the slides would be properly formatted. Unfortunately we have to result to these kinds of measures whenever the library doesnt work. but since this isn’t working properly as intended, im just going to change my UI to not use slides and hopefully this will become fixed soon.

The only way I could make it work when new slides are added dynamically was using directly the Swiper API.

initialSlides should never change. After the first render, any new images will be added using appendSlide

this is more or less what I did:

  const slidesRef = useRef<HTMLIonSlidesElement>(null);

  useEffect(() => {
    const handler = async (): Promise<void> => {
      const swiper = await slidesRef.current.getSwiper();
      swiper.appendSlide(
        `<ion-slide class='swiper-slide'><img class="landscape" alt decoding="async" src="${newUrl}"/></ion-slide>`
      );
      swiper.update();
    };
    handler();
  }, [newUrl]);

  return (
    <IonContent fullscreen scrollY={false}>
      {initialSlides.length && (
        <Slides ref={slidesRef}>
          {initialSlides.map(slide => (
            <IonSlide>blablabla</IonSlide>
          ))}
        </Slides>
      )}
    </IonContent>
  );

Is there a quick fix / workaround to this issue ?

@naishal I can show u my workaround. Did it with the help of @mrowe009 (https://github.com/ionic-team/ionic-framework/issues/18784#issuecomment-625689657). U’ll find the important part in line 30. But keep in mind, my application rarely updates the amount of slides.

import { IonSlides } from '@ionic/react';
import React from 'react';
import './Slides.css';
import Slide from '../Slide/Slide'
import SlideObject from '../../models/SlideObject';
import SlideOptsObject from '../../models/SlideOptsObject';

interface SlidesProps {
  slides: SlideObject[],
  slideOpts: SlideOptsObject,
  setActiveDate: Function
};

interface SlidesState {
};

class Slides extends React.Component<SlidesProps, SlidesState> {
  private slidesRef = React.createRef<any>();

  componentDidUpdate(prevProps: SlidesProps) {
    // IonSlides aktualisieren
    if (this.props.slides.length !== prevProps.slides.length) {
      this.slidesRef.current.update();
    }
  }

  render() {
    return (
      // key ggf. bei Bugfix von Ionic entfernen - https://github.com/ionic-team/ionic/issues/18784
      <IonSlides ref={this.slidesRef} scrollbar={true} options={this.props.slideOpts} key={this.props.slides.length}
        onIonSlideDidChange={async() => this.props.setActiveDate(this.props.slides[await this.slidesRef.current.getActiveIndex()].date)}
      >
        {this.props.slides.map((slide, i) => {
          return <Slide slide={slide} key={i} />;
        })}
      </IonSlides>
    );
  }
};

export default Slides;

@csaar95 okay, just keep in mind, the reason i said i didn’t like that hack is because whenever you change the key in react, it re-renders that component and its children. so if you have a few items, its ok, but if you have many, or child components that are render heavy, then you’ll see very laggy UI performance.

Same method that @javipascual , but i use the ReactDOMServer.renderToString() method in order to call my React Component into the appendSlide method.

Like this :

  const slidesRef: any = document.querySelector(`#${id}`);

  const next = async () => {
    const swiper: any = await slidesRef.getSwiper();
    swiper.appendSlide(generateSlides(limit));
    swiper.update();
    setLimit(limit + step);
  }

  const generateSlides = (offset = 0) => {
    let toReturn: any = [];
    txs.slice(offset , offset + step).map((tx: any, key: any) => {
      toReturn.push(
        ReactDOMServer.renderToString(
          <IonSlide key={key}>
            <TxCard tx={tx}></TxCard>
          </IonSlide>
        )
      )
    })

    return toReturn;
  }

  return (
    <div className="txs-slider">
      <div className="slider-context">
        <h2>{title}</h2>
        <p>{description}</p>
      </div>
      <IonSlides id={id} mode='md' scrollbar options={slideOpts} onIonSlideReachEnd={limit < 50 ? next : undefined}>
          {
            txs.slice(0, step).map((tx, key) => {
              return (
                <IonSlide key={key}>
                  <TxCard tx={tx}></TxCard>
                </IonSlide>
              );
            })
          }
        </IonSlides> 
    </div>
  );