Morgana
Morgana

Reputation: 337

Dynamically display photos of same item and hide carousel if there is only one photo

In a react app, I display 1-5 photos of each item. All the photos are in a folder under Node, so the way I display them is using their url, i.e. http://10.1.1.1:3000/01-0001.JPG So, I don't make an http request to get them.

Problem is:

It is not possible to know how many photos each item has. For example, one item could have 5 photos and another one only one photo. The format of the filename is this: 01-0001.JPG, 01-0001.1.JPG, 01-0001.2.JPG

So, what I currently do is this:

    const Item1 = () => {
      return (
        <img src={`${process.env.REACT_APP_NODE_SERVER}/${item.No}.JPG`} onError={this.addDefaultSrc} alt='1'></img>
      )
    }

    const Item2 = () => {
      return (
        <img src={`${process.env.REACT_APP_NODE_SERVER}/${item.No}.1.JPG`} onError={this.addDefaultSrc} alt='2'></img>
      )
    }

    const Item3 = () => {
      return (
        <img src={`${process.env.REACT_APP_NODE_SERVER}/${item.No}.2.JPG`} onError={this.addDefaultSrc} alt='3'></img>
      )
    }

and I use this function to display a dummy photo when there is no photo

  addDefaultSrc(ev) {
    ev.target.src = `${process.env.REACT_APP_NODE_SERVER}/notfound.png`;
    ev.target.onerror = null;
  }

This is bad solution because I don't want the client to see a dummy picture, instead client should see only the true number of photos for each item.

I am using react-multi-carousel

          <Carousel
          responsive={responsive}
          swipeable={false}
          draggable={false}
          showDots={true}
          ssr={false} // means to render carousel on server-side.
          infinite={false}
          autoPlay={false}
          autoPlaySpeed={1000}
          keyBoardControl={true}
          customTransition="all .5"
          transitionDuration={500}
          containerClass="carousel-container"
          removeArrowOnDeviceType={["tablet", "mobile"]}
          deviceType={this.props.deviceType}
          dotListClass="custom-dot-list-style"
          itemClass="carousel-item-padding-40-px">
            <div><Item1 /></div>
            <div><Item2 /></div>
            <div><Item3 /></div>
          </Carousel>

Any help would be much appreciated.

Upvotes: 1

Views: 557

Answers (1)

J&#243;zef Podlecki
J&#243;zef Podlecki

Reputation: 11283

I think you should loop over images, set initial style to display: none, wait for all callbacks and calculate appropriate state when everything is complete.

Example

const { useState, useEffect } = React;

const random = () => Math.floor(Math.random() * 5 + 5);
const getImages = () => Promise.resolve(Array(random()).fill(0).map((pr, index) => {

  const id = index + 750;

  return {
    id,
    url: `https://i.picsum.photos/id/${id}/100/100.jpg`,
    loaded: false,
    error: false,
    alt: id
  }
}))

const Images = ({onComplete}) => {
  const [images, setImages] = useState([]);
  const [allLoaded, setLoaded] = useState(false);
  
  useEffect(() => {
    let isUnmounted = false;
    
    getImages()
      .then(images => {
        if(isUnmounted) {
          return;
        }
        
        setImages(images);
      })
      
    return () => {
      isUnmounted = true;
    }
  }, [])
  
  useEffect(() => {
    let isUnmounted = false;
  
    const loadedImagesCount = images.reduce((acc, image) => acc + Number(image.error) + Number(image.loaded), 0);

    if(images.length === loadedImagesCount) {
      setTimeout(() => {
        if(isUnmounted) {
          return;
        }
        
        setLoaded(true);
        onComplete();
      }, 500);
    }
    
    return () => {
      isUnmounted = true;
    }
  }, [images])
  
  const onError = id => {
    setImages(images => {
      const imageIndex = images.findIndex(image => image.id ===id)
      const image = images[imageIndex];
      
      return [
        ...images.slice(0, imageIndex),
        {
          ...image,
          error: true
        },
        ...images.slice(imageIndex + 1)
      ]
    })
  }
  
  const onLoad = id => {
    setImages(images => {

      const imageIndex = images.findIndex(image => image.id ===id)
      const image = images[imageIndex];
      
      return [
        ...images.slice(0, imageIndex),
        {
          ...image,
          loaded: true
        },
        ...images.slice(imageIndex + 1)
      ]
    })
  }
  
  const loadedImagesCount = images.reduce((acc, image) => acc + Number(image.loaded), 0)
  
  if(allLoaded && [0, 1].includes(loadedImagesCount)) {
    return 'No images or only one image - hidden'
  }
  
  return <React.Fragment>
    {images.map(({id, loaded, error, url, alt}) => <img key={id} src={url} className={`image ${error ? 'image--error' : null} ${allLoaded && loaded ? 'image--loaded' : null}`} onLoad={() => onLoad(id)} onError={() => onError(id)} alt={alt}/>)}
  </React.Fragment>
}

const App = () => {
  const [loading, setLoading] = useState(true);

  const onComplete = () => {
    setLoading(false);
  }

  return <div>
    {loading ? 'Loading...' : null}
    <Images onComplete={onComplete}/>
  </div>
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
.image {
  display: none;
}

.image--error {
  display: none;
}

.image--loaded {
  display: inline;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>

Upvotes: 1

Related Questions