how to map array of images into two images per list item React JS

Viewed 1395

I want to map two images per list item.

I have a slider, that will show two slides per view, so I want it to show 4 images per view.

currently, its just showing the same image which is what I expected it to do, I just don't know how to map two different images per list item.

How would I go about mapping this array so that each list item has two different images?

SliderComponent.js

import React, { useState, useEffect, useRef } from "react";
import { IonGrid, IonRow, IonCol } from '@ionic/react';
import { Storage } from "aws-amplify";
import Slider from "react-slick";
import "slick-carousel/slick/slick.css";


const SliderComponent = (props) => {

    const { job } = props;
    const [images, setImages] = useState([]);


    const settings = {
        infinite: false,
        slidesToShow: 2,
        slidesToScroll: 2,
        speed: 500,
        rows: 1,
        initialSlide: 0,
        autoHeight: false,
        focusOnSelect: true,
        arrows: false,
    };


    useEffect(() => {
        async function onLoad() {

            try {
                const downloadedImage = await getImage(job);
                setImages(downloadedImage);
            } catch (e) {
                alert(e);
            }
        }

        onLoad();
    }, []);


    async function getImage(jobID) {
        const imageURL = await Storage.list(`${jobID}/completed/`);
        let imagesToDownload = imageURL
        let imagesAsArray = [];
        for (let i = 0; i < imagesToDownload.length; i++) {
            const imagesDownloaded = await getURLFromS3(imagesToDownload[i]);
            imagesAsArray.push(imagesDownloaded)
        }
        return imagesAsArray
    }

    async function getURLFromS3(fileToDownload) {
        const result = await Storage.get(fileToDownload.key)
        return result;
    }

   return (

        <div>
            <IonGrid>
                <IonRow style={{ justifyContent: 'center' }}>
                    <IonCol sizeXs="12" sizeSm="12" sizeMd="10" sizeLg="6" sizeXl="4" >
                        <Slider asNavFor={nav1} ref={slider => (slider2.current = slider)} {...viewSettings}>
                            {images.map((image, i) =>
                                <ul>
                                    <li><img key={i} src={image} /></li> /// First image
                                    <li><img key={i} src={image} /></li> /// Second image
                                </ul>
                            )}
                        </Slider>
                    </IonCol>
                </IonRow>
            </IonGrid>
        </div>
    )
}
export default SliderComponent;

props.job page

import React, { useState, useEffect } from "react";
import { withRouter } from 'react-router-dom';
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonGrid, IonCol, IonRow, } from '@ionic/react';
import { API, Auth } from "aws-amplify";
import SliderComponent from '../components/sliderComponent.js'


function propertyInfo(props) {


    return (
        <IonPage>
            <IonHeader>
                <IonToolbar>
                    <IonTitle>prop address</IonTitle>
                </IonToolbar>
            </IonHeader>
            <IonContent>

            <SliderComponent job={props.match.params.id} />

            </IonContent>
        </IonPage>
    );
}
export default withRouter(propertyInfo);
2 Answers

You could chunk the images array before mapping. If your application has lodash, you can use the _.chunk function:

lodash _.chunk(array, [size=1])

Creates an array of elements split into groups the length of size. If array can't be split evenly, the final chunk will be the remaining elements.

You can also write your own chunk function in a few lines of code:

function chunk(array, size) {
  const chunked_arr = [];
  let index = 0;
  while (index < array.length) {
    chunked_arr.push(array.slice(index, size + index));
    index += size;
  }
  return chunked_arr;
}

Result (Note: as @HMR pointed out, image2 is rendered conditionally because if your images array is an odd number, your last chunk will only have one element and would render a broken image tag):

{chunk(images, 2).map(([image1, image2], i) =>
  <ul key={i}>
    <li><img src={image1} /></li>
    {image2 && <li><img src={image2} /></li>}
  </ul>
)}

You can use zip, it takes x arrays and will return an array where each element is an element of the arrays passed. So for [1,2] and [3,4] it will return [[1,3],[2,4]].

When you pass the same array but second array is slice(1) (without the first element) you would pass [1,2] and [2] resulting in [[1,2],[2,undefined]], then filter out all elements that are in a odd index and you'll get [[1,2]]. Here is an example:

const zip = (...arrays) =>
  [
    ...new Array(
      Math.max(...arrays.map((array) => array.length))
    ),
  ].map((_, index) => arrays.map((array) => array[index]));
const array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
console.log(
  '2 imamages',
  zip(array, array.slice(1)).filter(
    (_, index) => index % 2 === 0
  )
);
console.log(
  '3 imamages',
  zip(array, array.slice(1), array.slice(2)).filter(
    (_, index) => index % 3 === 0
  )
);

So now you can do:

{zip(images, images.slice(1))
  .filter((_, index) => index % 2 === 0)
  .map(([image1,image2], i) =>
  <ul key={i}>
      <li><img src={image1} /></li> /// First image
      <li>{image2 && <img src={image2} />}</li> /// Second image
  </ul>
)}

Please note that if you have an odd number of images then at some point image2 is undefined that is why the second image is rendered conditionally if there is an image. I also corrected the key prop, you need to set that on the root element.

If you like chunk better then once you have zip chunk is a piece of cake:

const zip = (...arrays) =>
  [
    ...new Array(
      Math.max(...arrays.map((array) => array.length))
    ),
  ].map((_, index) => arrays.map((array) => array[index]));
const chunk = (array, items) =>
  zip(
    ...[...new Array(items)].map((_, index) =>
      array.slice(index)
    )
  ).filter((_, index) => index % items === 0);
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log('2 imamages', chunk(array, 2));
console.log('3 imamages', chunk(array, 3));

Related