nolimits4web / swiper

Most modern mobile touch slider with hardware accelerated transitions
https://swiperjs.com
MIT License
40.05k stars 9.75k forks source link

Children does not render properly (after wrapper component) #4413

Closed tobiaszciesielski closed 2 years ago

tobiaszciesielski commented 3 years ago

Hello, I have already posted on stackoverflow about rendering children in swiperjs using React.

https://stackoverflow.com/questions/66993033/swiper-js-for-react-does-not-render-children-properly I use:

"gatsby": "^3.0.1",
"react": "^17.0.1",
"swiper": "^6.5.0",

What You Did

I want to render children like this: <Swiper>{children}<Swiper/> But children are rendered after swiper wrapper.

Expected Behavior

I want to pass all children and render them inside Swiper wrapper because I fetch data from external source (headless cms)

Actual Behavior

Swiper Slides are rendered after Swiper component what makes cards invisible in page content.

The code you can see in stack overflow and this commit of my project https://github.com/tobiaszciesielski/tciesielski.pl/pull/1/commits/5500865d7eef1a99facb03e3dedd987ee1ce6033

alicerocheman commented 3 years ago

Same issue.

react

Slider.tsx


import { FC, useMemo } from 'react';
import { Swiper } from 'swiper/react';
import { Navigation, Scrollbar, A11y, Keyboard } from 'swiper';

const breakpoints = { 1100: { slidesPerView: 4, }, 768: { slidesPerView: 3, }, 464: { slidesPerView: 2, }, };

const Slider: FC = ({ children }) => { return ( <> <Swiper wrapperTag="ul" modules={[Navigation, Scrollbar, A11y, Keyboard]} navigation={{ prevEl: '.prev', nextEl: '.next' }} slidesPerView={1} spaceBetween={16} breakpoints={breakpoints}

{children} </> ); };

export default Slider;


> Component.tsx
```tsx
return (
  <Slider>
    {categories.map((category) => (
      <>
        category.list.map(el => (<SwiperSlide tag="li">{el.content}</SwiperSlide>))
      </>
    )}
  </Slider>
);

renders:

<div class="swiper swiper-initialized swiper-horizontal swiper-android w-full">
  <ul
    class="swiper-wrapper"
    id="swiper-wrapper-dc73d3daab7e6247"
    aria-live="polite"
    style="transform: translate3d(0px, 0px, 0px)"
  ></ul>
  <span
    class="swiper-notification"
    aria-live="assertive"
    aria-atomic="true"
  ></span
  ><span
    class="swiper-notification"
    aria-live="assertive"
    aria-atomic="true"
  ></span>
  <li class="swiper-slide"></li>
  <li class="swiper-slide"></li>
  <li class="swiper-slide"></li>
  <li class="swiper-slide"></li>
  <li class="swiper-slide"></li>
  <li class="swiper-slide"></li>
</div>·

Expected behavior

slides render inside the swiper-wrapper

Current behavior

slides render outside the swiper-wrapper

jomarquez21 commented 3 years ago

Same error in 7.2.0 version

jomarquez21 commented 3 years ago

@nolimits4web Can you confirm if this is a bug or a misinterpretation of the documentation?

jomarquez21 commented 3 years ago

@tobiaszciesielski Can you confirm if this is a bug or a misinterpretation of the documentation?

github-actions[bot] commented 3 years ago

Hello @tobiaszciesielski. Please provide a online reproduction by codesandbox or a minimal GitHub repository. You can fork one of our demos in codesandbox to get start. Issues labeled by missing demo will be closed if no activities in 3 days.

jomarquez21 commented 3 years ago

Demo here https://codesandbox.io/s/error-if-swiperslide-is-wrapped-in-a-component-qbtc0?file=/src/Demo.jsx

jomarquez21 commented 3 years ago

@nolimits4web Could you remove the "missing demo" tag?

williamgoulois commented 3 years ago

@jomarquez21 I encoutered same issue when i tried to use subcomponents and here is the workaroud i found :

Carousel.tsx


import React, { ReactNode } from 'react'
import { Swiper, SwiperSlide } from 'swiper/react'
import SwiperCore, { Keyboard, Mousewheel, Navigation } from 'swiper'

type CarouselComposition = { Item: typeof Item }

export type CarouselProps = { children: ReactNode }

SwiperCore.use([Navigation, Mousewheel, Keyboard])

export const Carousel: React.VFC & CarouselComposition = ({ children }) => { return (

{/* workaround because swiper needs SwiperSlide as direct children @see https://github.com/nolimits4web/swiper/issues/4413 @see https://github.com/nolimits4web/swiper/issues/4084 */} {React.Children.map(children, (child: ReactNode) => ( {child} ))} ) } type ItemProps = { children: ReactNode } const Item = ({ children }: ItemProps) => { return <>{children} } Carousel.Item = Item ``` > Component.tsx ```tsx ... return ( {Array.from(Array(10).fill(0)).map((_, i) => { return ( Slide {i} ) })} ) ... ```
jomarquez21 commented 3 years ago

hi! @williamgoulois, I also used a workaround a bit similar to your code.

{items.map((item, index) => {
  // `Swiper` to render the slider items requires its children property to be a collection of `SwiperSlider` avoiding being able to create reusable slider components.
  // This workaround is created to assign a key to each `SwiperSlider`
  /** {@todo This workaround should be removed when this issue https://github.com/nolimits4web/swiper/issues/4413 is resolved}. */

  return (
    <SwiperSlide key={`slide_${index}_${item.key}`}>
      {React.cloneElement(item)}
    </SwiperSlide>
  )
})}
im-ironclad commented 2 years ago

In case it helps anyone, I just recently faced this issue as well but in this context:

I was pulling from different components that each were wrapped in a SwiperSlide (inside the component). Reading up on the above shows that Swiper wants the SwiperSlides to be a direct child so to get it working I removed the SwiperSlide inside each component and wrapped them with SwiperSlide outside of the component, inside the Carousel component.

From

Carousel.tsx

case 'newsArticles_default_Entry':
  return (
      <NewsArticleSlide content={content} />
  );
case 'podcasts_default_Entry':
  return (
      <PodcastSlide content={content} />
  );

Slide.tsx

return (
    <SwiperSlide key={id}>
        <article>
            ...markup
        </article>
    </SwiperSlide>
  )

To

Carousel.tsx

case 'newsArticles_default_Entry':
  return (
    <SwiperSlide key={content.id}>
      <NewsArticleSlide content={content} />
    </SwiperSlide>
  );
case 'podcasts_default_Entry':
  return (
    <SwiperSlide key={content.id}>
      <PodcastSlide content={content} />
    </SwiperSlide>
  );

Slide.tsx

return (
    <article>
        ...markup
    </article>
  )
jomarquez21 commented 2 years ago

@nolimits4web is this considered an error? If this is a bug, can you add the corresponding tag?

msoyka commented 2 years ago

Had the same issue, and fixed it with an even simpler solution. Let me know if you see any concerns.

All I needed to do was change the displayName of my 'custom slide' component to match the "SwiperSlide" component so the parent "Swiper" thinks the direct child is "SwiperSlide".

export const BannerSliderSlide = () => {
  return (
    <SwiperSlide>
          // Add custom code here...
    </SwiperSlide>
  );
};

BannerSliderSlide.displayName = 'SwiperSlide'; <-- ** ADDED THIS **
isaacbatst commented 2 years ago

Thanks for the solution @msoyka.

Sharing a 'deeper slides' situation here. Just the first level needs the edit.

// Root
<Swiper>
  <SwiperSlide className={styles.planningTypesSlide}> // not nested, no problem at all
     <PlanningTypesSlide />  
  </SwiperSlide>
  <NestedSlides/> // displayName needs editing
</Swiper>
// NestedSlides.jsx
<>
  <SwiperSlide className={styles.planningTypesSlide}> // not nested, no problem at all
     <AnotherCustomSlide />  
  </SwiperSlide>
  <DeeperNestedSlides /> // surprisingly, no need to edit displayName here
</>
rardoz commented 2 years ago

This makes me really sad.

michaelschufi commented 2 years ago

Had the same issue. Thanks for the workaround!

Two points: