Search code examples
node.jsreactjscarousel

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.


Solution

  • 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>