2

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
  • can you include the props.job code? Commented May 20, 2020 at 16:49
  • @Skoltz edited! Commented May 20, 2020 at 16:56

2 Answers 2

3

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>
)}
Sign up to request clarification or add additional context in comments.

2 Comments

Will cause an error if images is an odd number. And adding lodash for something you could write in 12 lines of code is maybe not the best idea
@HMR - You're correct, there would have been a broken image tag if the last chunk only contained 1 element. I modified the example to handle this case. I also added a custom chunk function if lodash is not an option.
0

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));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.