react Carousel from scratch logic with visibility - javascript

what's the logic or algorithm for a Carousel? I'm done my research for 1 item per display but in my case I need to display 3 items, when I click on the next I expect the first item is hidden and the 4th item appear.
<div className="App">
<div className="Container">
{items.map((item, i) => (
<div className={`item ${i < lastIndex ? "visible" : "hidden"}`}>
{item.name}
</div>
))}
</div>
<div className="prev">{"<"}</div>
<div className="next">{">"}</div>
</div>
https://codesandbox.io/s/frosty-babbage-djron?file=/src/App.js

You will have to keep track of the first and last index that should be displayed. Anything outside them, you can skip
import React, { useState } from "react";
import "./styles.css";
const items = [
{
name: "box 1"
},
{
name: "box 2"
},
{
name: "box 3"
},
{
name: "box 4"
},
{
name: "box 5"
}
];
export default function App() {
const [firstIndex, setFirstIndex] = useState(0);
const [lastIndex, setLastIndex] = useState(3);
return (
<div className="App">
{/* <h1>{lastIndex}</h1> */}
<div className="Container">
{items.map((item, i) => {
if (i >= firstIndex && i < lastIndex) {
return (
<div key={item.name} className={"visible"}>
{item.name}
</div>
);
} else {
return null;
}
})}
</div>
<div
className="prev"
onClick={() => {
if (firstIndex > 0) {
setFirstIndex(firstIndex - 1);
setLastIndex(lastIndex - 1);
}
}}
>
{"<"}
</div>
<div
className="next"
onClick={() => {
if (lastIndex < items.length) {
setFirstIndex(firstIndex + 1);
setLastIndex(lastIndex + 1);
}
}}
>
{">"}
</div>
</div>
);
}
https://codesandbox.io/s/elated-banach-6bfh2?file=/src/App.js
EDIT Updated the code to prevent first and last index going out of bound of items length

I hope this will help for you!
export default function App() {
const [startIndex, setStartIndex] = useState(1);
const [lastIndex, setLastIndex] = useState(3);
return (
<div className="App">
<div className="Container">
{
items.map((item, i) => {
if (i >= startIndex - 1 && i <= lastIndex)
return (
<div className={`item ${i < lastIndex ? "visible" : "hidden"}`} key={i}>
{item.name}
</div>
)
})
}
</div>
<div className="prev"
onClick={() => {
setStartIndex(startIndex - 1)
setLastIndex(lastIndex - 1)
}}
>{"<"}</div>
<div className="next"
onClick={() => {
setStartIndex(startIndex + 1)
setLastIndex(lastIndex + 1)
}}
>
{">"}
</div>
</div>
);
}

Related

two React Componets in One section we upload image and can increase the number of input other section we will preview those images

I am working on a page
have two sections. In one section we upload image and can increase the number of input, in the other section we will preview the image.
I don't know I am not getting the right id that I want to edit.
we can add more fields on click of button and remove then at the end post data at api endpoint?
RightSection
const RightSection = ({ inputImage, setInputImage }) => {
const handleAddFields = () => {
if (inputImage.length <= 9) {
const values = [...inputImage];
values.push({
id: values.length + 1,
name: `Drop Image ${values.length + 1} Here`,
});
setInputImage(values);
}
};
const handleRemoveFields = () => {
if (inputImage.length > 3) {
const values = [...inputImage];
values.splice(values.length - 1, 1);
setInputImage(values);
}
};
const handleInputChange = (id, event) => {
console.log(id, event.target.id, "=====");
const newInputFields = inputImage.map((i) => {
if (id === i.id) {
i.url = URL.createObjectURL(event.target.files[0]);
i.name = event.target.files[0].name;
// push image object in array
setInputImage([
...inputImage,
{
id: id,
url: URL.createObjectURL(event.target.files[0]),
name: event.target.files[0].name,
},
]);
}
return i;
});
setInputImage(newInputFields);
};
console.log(inputImage);
return (
<>
<div id="right" className="flex">
<div className="margin">
<div className="inlineflex">
<H1>Background Image</H1>
<div>
<AddIcon onClick={handleAddFields} />
<RemoveIcon onClick={handleRemoveFields} />
</div>
</div>
</div>
<div
style={{
margin: "0 auto",
position: "relative",
width: "80%",
}}
>
{inputImage.map((inputField, index) => (
<FileInput key={index}>
<label
htmlFor={inputField.id}
onClick={(e) => {
console.log("click", index + 1);
}}
>
{inputField.name}
</label>
<input
id={index + 1}
onChange={(event) => handleInputChange(inputField.id, event)}
accept="image/*"
type="file"
/>
</FileInput>
))}
</div>
</div>
</>
);
};
LeftSection
const LeftSection = ({ inputImage }) => {
return (
<>
<div id="left" className="flex">
<div className="margin">
<H1>Preview</H1>
</div>
<Grid>
{Array.isArray(inputImage) &&
inputImage.map((item, index) => {
if (item?.url?.includes("http") || item?.url?.includes("https")) {
return (
<div key={index}>
<img src={item?.url} alt={item?.name} />
</div>
);
}
})}
</Grid>
</div>
</>
);
};
BackgroundImage
let initaValue = [
{ id: 1, name: "Drop Image 1 Here", url: "" },
{ id: 2, name: "Drop Image 2 Here", url: "" },
{ id: 3, name: "Drop Image 3 Here", url: "" },
];
const BackgroundImage = () => {
const [inputImage, setInputImage] = useState(initaValue);
return (
<>
<Container>
<RightSection inputImage={inputImage} setInputImage={setInputImage} />
<LeftSection inputImage={inputImage} setInputImage={setInputImage} />
</Container>
</>
);
};
export default BackgroundImage;
I think there is some issue with the handleInputChange function on the RightSection component.
Somehow I am not able to update the item with the correct id in the array.
Is there any other efficient solution for this problem?

Warning: Text content did not match in React 18

recently the project I am working on has been upgraded to React 18. By then, suddenly a lot of issues with hydration have started to appear as warnings/errors in the console. The one I'm struggling with is "Warning: Text content did not match":
Code of this component:
<div className="O75-product-faq__questions is-active accordion--initialized">
{
dataForSelect.length > 1 && <h4 className="O75-product-faq__questions__name js-category-name">{props.questionsByCategories[selectedCategory?.value].name}</h4>
}
{
props.questionsByCategories[selectedCategory?.value].questions.map((element, i) => (
<div key={i} className="O75-product-faq__questions__item">
{(element.question || props.showOnlyAnswer) && <div className={`O75-product-faq__questions__item__button${openedElement === i ? ' has-accordion-open' : ''}`} onClick={() => openElement(i)}>{element.question}</div>}
<AnimateHeight height={openedElement === i ? 'auto' : 0} duration={transitionDisabled ? 0 : 400}>
<div className="O75-product-faq__questions__item__content" dangerouslySetInnerHTML={{ __html: element.answer }} />
</AnimateHeight>
</div>))
}
</div>
I know that this issue results from the difference between client and server side rendering, but I don't know how to fix it and no other similar question contained solution that worked in my case.
Rest of the file, in case if the issue is not with aforementioned part:
import React, { useMemo, useState } from 'react';
import type { ReactElement } from 'react';
import AnimateHeight from 'react-animate-height';
import { BaseSelect, SelectOption } from '../molecules/base-select';
import type { FrequentlyAskedCategory } from './frequentlyAskedQuestion';
import { fromBase64 } from '../shared-services/base64Service';
interface FaqPanelProps {
mainTitle?: string;
menuTitle?: string;
showOnlyAnswer: boolean;
currentPageUrl: string;
questionsByCategories: Record<string, FrequentlyAskedCategory>,
faqStructuredDataBase64: string;
}
const FAQPanel = (props: FaqPanelProps): ReactElement => {
const categories = Object.keys(props.questionsByCategories);
const dataForSelect: Array<SelectOption> = categories.map(key => ({ label: props.questionsByCategories[key].name, value: key }));
const noOpenedElementIndex = -1;
const [openedElement, setOpenedElement] = useState<number>(-1);
const [selectedCategory, setSelectedCategory] = useState<SelectOption>(dataForSelect.length > 0 ? dataForSelect[0] : null);
const [transitionDisabled, setTransitionDisabled] = useState<boolean>(false);
const parsedStructuredData = useMemo(() => {
if(props.faqStructuredDataBase64 != null && props.faqStructuredDataBase64 !== ""){
return fromBase64(props.faqStructuredDataBase64);
}
return "";
}, [props.faqStructuredDataBase64]);
const selectNewCategory = (option: SelectOption): void => {
setTransitionDisabled(true);
setOpenedElement(noOpenedElementIndex);
setSelectedCategory(option);
}
const openElement = (index: number): void => {
if (transitionDisabled) {
setTransitionDisabled(false);
}
setOpenedElement(index === openedElement ? noOpenedElementIndex : index);
}
const speakableJson = JSON.stringify({
"#context": "https://schema.org/",
"#type": "WebPage",
"name": props.mainTitle,
"speakable":
[".O75-product-faq__headline",
".O75-product-faq__questions__item"],
"url": props.currentPageUrl
});
const hasFAQStructuredData = parsedStructuredData != null && parsedStructuredData !== "";
return (
<div className="container">
<section className="O75-product-faq" >
{
props.mainTitle
? <h2 className="O75-product-faq__headline">{props.mainTitle}</h2>
: <h4 className="O75-product-faq__categories-headline">{props.menuTitle}</h4>
}
<div className="flex">
{dataForSelect.length > 1 &&
<div className="O75-product-faq__categories">
<div className="filter__list is-hidden-sm filter">
{
dataForSelect.map((element, i) => (
<button className={`filter__btn js-filter__btn${element.value === selectedCategory?.value ? " is-active" : ""}`} key={i} onClick={() => selectNewCategory(element)}>
{element.label}
</button>))
}
</div>
<div className="filter__group is-hidden-md">
<BaseSelect selectedValue={selectedCategory}
handleChange={selectNewCategory}
options={dataForSelect} />
</div>
</div>
}
{categories.length > 0 &&
<div className="O75-product-faq__questions is-active accordion--initialized">
{
dataForSelect.length > 1 && <h4 className="O75-product-faq__questions__name js-category-name">{props.questionsByCategories[selectedCategory?.value].name}</h4>
}
{
props.questionsByCategories[selectedCategory?.value].questions.map((element, i) => (
<div key={i} className="O75-product-faq__questions__item">
{(element.question || props.showOnlyAnswer) && <div className={`O75-product-faq__questions__item__button${openedElement === i ? ' has-accordion-open' : ''}`} onClick={() => openElement(i)}>{element.question}</div>}
<AnimateHeight height={openedElement === i ? 'auto' : 0} duration={transitionDisabled ? 0 : 400}>
<div className="O75-product-faq__questions__item__content" dangerouslySetInnerHTML={{ __html: element.answer }} />
</AnimateHeight>
</div>))
}
</div>
}
</div>
{hasFAQStructuredData && <script suppressHydrationWarning type="application/ld+json" dangerouslySetInnerHTML={{__html:parsedStructuredData } }></script>}
<script suppressHydrationWarning type="application/ld+json" dangerouslySetInnerHTML={{__html: speakableJson}} ></script>
</section>
</div>
)
}
export { FAQPanel };
export type { FaqPanelProps }
Does anyone knows how to fix it?

how to map image array with filter in ReactJS

I'm absolutely beginner in ReactJS and I want to filter and map
const Shop = [
{
img : jam,
category: 'jam'
},
{
img : headset,
category:'headset'
},
{
img : sepatu,
category:'sepatu'
}
];
let ShopItem = [
{
id : 1,
img : jam,
category : 'jam',
price : 'Rp. 900,000'
},
{
id : 2,
img : jam,
category : 'jam',
price : 'Rp. 900,000'
},
{
id : 3,
img : headset,
category : 'headset',
price : 'Rp. 900,000'
}
]
function Content(){
const [shopItems,toogleshopItems]=useState(false);
let [items,showItems]=useState('');
const toogleShop = ()=>{
toogleshopItems(!shopItems);
console.log(shopItems);
}
function showshopItems(){
return(
<>
{Shopitem.map((shopitem, shopitemIndex) => {
return Shop.map((shop, shopIndex) => {
if (shop.category == shopitem.category)
return <img className="image-place" src={shopitem.img} />;
});
})}
</>
)
}
return(
<>
<div className="content">
<div className="content-wrapper">
<div className="content-title">Browse Categories</div>
<div className="image-flex">
{
Shop.map((shops)=>
<React.Fragment key={shops.category}>
<img onClick={toogleShop} className="image-place" src={shops.img}/>
</React.Fragment>
)}
</div>
<hr/>
{shopItems? showshopItems() : console.log("no") }
</div>
</div>
</>
)
}
export default Content
how to map shopitem image that has the same category value with shop category value...
when I click picture watch
if I click the picture 1 it will show child picture that has category jam
Finally I found out whats your problem. You should have a state for getting current category when user clicks on the top image and filter the items based on the state. I've created a sample based on your request:
export default function App() {
const [currentCategory, setCategory] = useState();
const TopSlide = () => {
return (
<div className="topSlide">
{Shop.map((shop, index) => {
return (
<img
alt=""
className="topSlideImage"
src={shop.img}
onClick={() => {
setCategory(shop.category);
}}
/>
);
})}
</div>
);
};
const DownSlide = () => {
return (
<div className="downSlide">
{ShopItem.filter((x) => x.category === currentCategory).map(
(shop, index) => {
return <img alt="" className="downSlideImage" src={shop.img} />;
}
)}
</div>
);
};
return (
<div className="App">
{TopSlide()}
<hr />
{DownSlide()}
</div>
);
}

.map is not a function in react when calling a carousel component on async props element

I have an already working site. I am working on updating a react template by adding a carousel or image slider at the bottom of the page. The idea is to display images from a folder using a mongodb collection. I created the carousel/slider component as follows:
import React, { Intl } from "react";
import Carousel from "react-multi-carousel";
const DeviceCarousel = ({ ImagesDevices }) => {
const responsive = {
superLargeDesktop: {
// the naming can be any, depends on you.
breakpoint: { max: 4000, min: 3000 },
items: 5,
},
desktop: {
breakpoint: { max: 2999, min: 1024 },
items: 3,
},
tablet: {
breakpoint: { max: 1023, min: 464 },
items: 2,
},
mobile: {
breakpoint: { max: 463, min: 0 },
items: 1,
},
};
return (
<div id='news'>
<div className='container'>
<h1>News & Articles</h1>
<Carousel responsive={responsive}>
{ImagesDevices.map((deviceimage) => {
const imagepath =`/images/gallery/${deviceimage.filename}`;
return (
<div className='carouselItem'>
<img src={imagepath} alt='Image' />
<div className='caption'>
<div className='caption-inner'>
<h4 className='no-bar'>{deviceimage.caption}</h4>
<h5 className='no-bar'>
{deviceimage.copyright}
</h5>
</div>
</div>
</div>
);
})}
</Carousel>
</div>
</div>
);
};
export default DeviceCarousel;
I then went on to call the component in my main template:
import Navbar from "../../components/Header";
import Footer from "../../components/Footer";
import ArticleInfo from "../../components/ArticleInfo";
import dynamic from 'next/dynamic';
import CompanyLink from "../../components/crossLinks/companyLink";
import ResearchGroupLink from "../../components/crossLinks/researchGroupLink";
import DeviceCarousel from "../../components/devices/DeviceCarousel";
const ImageLink = dynamic(() => import("../../components/crossLinks/imageLink"), {ssr: false});
const PublicationLink = dynamic(() => import("../../components/crossLinks/publicationLink"), {ssr: false});
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faBrain, faClock, faCheckCircle, faTimesCircle,
faHospitalUser, faAward, faBan, faUsers } from "#fortawesome/free-solid-svg-icons";
import Device from "../../models/Device";
import Company from "../../models/Company";
import ResearchGroup from "../../models/ResearchGroup";
import University from "../../models/University";
import Person from "../../models/Person";
import DeviceImageGallery from "../../models/DeviceImageGallery";
import dbConnect from "../../utils/dbConnect";
import Error from "next/error";
import axios from "axios";
import React, { useState, useEffect } from "react";
function getArticles(name) {
let nameArr = name.split(" ");
let queryString = "";
nameArr.forEach(function (item, index) {
queryString += item;
if (index !== nameArr.length - 1) {
queryString += "%20";
}
});
const URL = `xxxxxxxxxxxxxxxxxxxxxxx=${queryString}`;
return axios.get(URL, {
headers: { "Zotero-API-Version": "3" },
});
}
const DeviceTemplate = ({ errorCode, data,ImagesDevices}) => {
if (errorCode) {
return <Error statusCode={errorCode} />;
}
const deviceInfo = JSON.parse(data);
deviceInfo.researchGroupsId = JSON.parse(deviceInfo.researchGroupsId);
deviceInfo.commercialPartnersId = JSON.parse(deviceInfo.commercialPartnersId);
deviceInfo.authorId = JSON.parse(deviceInfo.authorId);
deviceInfo.verifiedId = JSON.parse(deviceInfo.verifiedId);
const [articles, setArticles] = useState([]);
useEffect(() => {
getArticles(name).then(function (result) {
setArticles(result.data);
});
}, []);
const {
name,
queryName,
researchGroupsId,
commercialPartnersId,
status,
implantLocation,
numImplantedToDate,
summary,
background,
videoLink,
clinicalTrials,
imageProcessorLocation,
elecNumber,
elecLayout,
elecDiameter,
elecPitch,
firstPatientYear,
fdaApprovalYear,
ceMarkYear,
endOfProductionYear,
lastUpdate,
authorId,
verifiedId
} = deviceInfo;
const imagePath = `/images/logos/devices/${queryName}.jpg`;
return (
<div>
<Navbar scrollEffect={true} />
<div id='device-profile'>
<div className='container'>
<div className='row mb-4'>
<div className='col-xs-3 col-sm-3 col-md-3'>
<ImageLink
id='device-logo'
className='border-image'
src={imagePath}
alternativeImage="/images/logos/devices/device.png"
/>
</div>
<div id='device-info' class='col-xs-9 col-sm-9 col-md-9'>
<h2>{name}</h2>
{ implantLocation && (
<p><FontAwesomeIcon icon={faBrain} size='1x' alt='implant location' />{implantLocation}</p>
)}
{ numImplantedToDate && (
<p><FontAwesomeIcon icon={faUsers} size='1x' alt='implant location' />{numImplantedToDate} implanted</p>
)}
{ status == "active" && (
<p><FontAwesomeIcon icon={faCheckCircle} size='1x' alt='active' />active</p>
)}
{ status == "inactive" && (
<p><FontAwesomeIcon icon={faTimesCircle} size='1x' alt='inactive' />inactive</p>
)}
</div>
</div>
{ summary && (
<div className='row mb-3'>
<p>{summary}</p>
</div>
)}
{ commercialPartnersId.length !== 0 && (
<div className='row mb-3'>
<h3>Commercial Partners {queryName} {ImagesDevices} </h3>
<ul className='cross-links d-flex flex-wrap p-1'>
{commercialPartnersId.map(partner => (
<CompanyLink
name={partner.name}
queryName={partner.queryName}
city={partner.city}
country={partner.country} />
))
}
</ul>
</div>
)}
{ researchGroupsId.length !== 0 && (
<div className='row mb-3'>
<h3>Academic Collaborators </h3>
<ul className='cross-links d-flex flex-wrap p-1'>
{researchGroupsId.map(partner => (
<ResearchGroupLink
name={partner.name}
queryName={partner.queryName}
university={partner.universityId && partner.universityId.name} />
))
}
</ul>
</div>
)}
{ (firstPatientYear || fdaApprovalYear || ceMarkYear || endOfProductionYear) && (
<div className='row mb-3'>
<h3>Milestones</h3>
<ul className='milestones'>
{ firstPatientYear && (
<li><FontAwesomeIcon icon={faHospitalUser} size='1x' alt='first patient'/>{firstPatientYear}: First patient implanted</li>
)}
{ ceMarkYear && (
<li><FontAwesomeIcon icon={faAward} size='1x' alt='CE mark approval'/>{ceMarkYear}: CE mark approval</li>
)}
{ fdaApprovalYear && (
<li><FontAwesomeIcon icon={faAward} size='1x' alt='FDA approval'/>{fdaApprovalYear}: FDA approval</li>
)}
{ endOfProductionYear && (
<li><FontAwesomeIcon icon={faBan} size='1x' alt='End of production'/>{endOfProductionYear}: End of production</li>
)}
</ul>
</div>
)}
{ (background || videoLink) && (
<div className='row mb-4'>
<h3>Background</h3>
<p dangerouslySetInnerHTML={{ __html: background }} />
<div dangerouslySetInnerHTML={{ __html: videoLink }} />
</div>
)}
{ (elecNumber || elecLayout || elecDiameter || elecPitch || imageProcessorLocation) && (
<div className='row mb-3'>
<h3>Device Specifications</h3>
<ul>
{ elecNumber && (
<li>Number of electrodes: {elecNumber}</li>
)}
{ elecLayout && (
<li>Electrode layout: {elecLayout}</li>
)}
{ elecDiameter && (
<li>Electrode diameter: {elecDiameter} μm</li>
)}
{ elecPitch && (
<li>Electrode pitch: {elecPitch} μm</li>
)}
{ imageProcessorLocation && (
<li>Image processor location: {imageProcessorLocation}</li>
)}
</ul>
</div>
)}
{ clinicalTrials.length !== 0 && (
<div className='row mb-3'>
<h3>Clinical Trials </h3>
<ul>
{ clinicalTrials.map(item => (
<li><a href={`xxxxxxxxxx/${item}`} target='_blank'>{item}</a></li>
))}
</ul>
</div>
)}
{articles.length !== 0 && (
<div className='row mb-3'>
<h3>Selected Publications </h3>
<ul className='cross-links'>
{articles.map(article => (
<PublicationLink
authors={article.data.creators}
title={article.data.title}
date={article.data.date}
journal={article.data.publicationTitle}
conference={article.data.proceedingsTitle}
doi={article.data.DOI}
issue={article.data.issue}
volume={article.data.volume}
pages={article.data.pages}
type={article.data.itemType} />
))
}
</ul>
</div>
)}
<div className='row mb-4'>
<ArticleInfo lastUpdate={lastUpdate} authorId={authorId} verifiedId={verifiedId}/>
<DeviceCarousel ImagesDevices={ImagesDevices}/>
</div>
</div>
</div>
<Footer />
</div>
);
};
export async function getServerSideProps({ query: { deviceName } }) {
dbConnect();
const deviceResult = await Device.findOne({
queryName: deviceName,
})
.populate({ path: "researchGroupsId", model: ResearchGroup,
select: "name university queryName",
populate: { path: "university", model: University, select: "name" }})
.populate({ path: "commercialPartnersId", model: Company,
select: "name queryName city country yearFounded yearDissolved" })
.populate({ path: "authorId", model: Person,
select: "firstName preferredName lastName queryName researchGroupId",
populate: { path: "researchGroupId", model: ResearchGroup, select: "universityId",
populate: { path: "universityId", model: University, select: "name" } } })
.populate({ path: "verifiedId", model: Person,
select: "firstName preferredName lastName queryName researchGroupId",
populate: { path: "researchGroupId", model: ResearchGroup, select: "universityId",
populate: { path: "universityId", model: University, select: "name" } } });
const thedevice=await Device.findOne({queryName:deviceName});
const iddevice=thedevice._id;
const errorCode = deviceResult ? false : 400;
deviceResult.researchGroupsId = JSON.stringify(deviceResult.researchGroupsId);
deviceResult.commercialPartnersId = JSON.stringify(deviceResult.commercialPartnersId);
deviceResult.authorId = JSON.stringify(deviceResult.authorId);
deviceResult.verifiedId = JSON.stringify(deviceResult.verifiedId);
deviceResult.lastUpdate = JSON.stringify(deviceResult.lastUpdate);
//the code that we changed is below
deviceResult._id=JSON.stringify(deviceResult._id);
const deviceImages = await DeviceImageGallery.find({deviceId:deviceResult._id}).sort({order:1});
//
return {
props: { errorCode: errorCode, data: JSON.stringify(deviceResult),ImagesDevices:JSON.stringify(deviceImages) },
};
}
export default DeviceTemplate;
ImagesDevices is an array that was arrived at in the async function below through querying of the mongodb collection, however when I call
<DeviceCarousel ImagesDevices={ImagesDevices}/>
as seen in the code I am given the error that the ImagesDevices.map in the slider component(the first set of code) is not a function. the ImagesDevices element that I arrived at from the second set of code when passed through the slider component seems to be giving this error.
I have read that this error happens when data is not in array format. I converted my ImagesDevices element into an array even though it already was. I am however still getting this error. everything was rendering fine until I made that call in my template. Any ideas on where my code is wrong? or am I calling my slider component wrong?

How to make a simple loop slider in the React?

Sorry for my English)
Do not judge strictly, since I just started working with the react.
I made a simple slider on the react and now I want to make it cyclic.
But I can’t. In my code it seems to be cyclic, but for some reason it skips the last picture.
how can i fix it?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{
id: 1,
name: "Product 1",
price: 50,
q: 0,
category: "Sporting Goods",
images: [
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg"
],
currentImageIndex: 0,
isCycleMode: false,
cantGoPrev: false,
cantGoNext: true
},
{
id: 2,
name: "Product 2",
price: 70,
q: 0,
category: "Sporting Goods",
images: [
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg",
"https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg"
],
currentImageIndex: 0,
isCycleMode: false,
cantGoPrev: false,
cantGoNext: true
}
]
};
}
nextSlideHandler = (e, item, index ) => {
let arr = [...this.state.data];
let newIndex = this.state.data[index].currentImageIndex;
if (e.currentTarget.dataset.direction === "next") {
if (newIndex < this.state.data[index].images.length - 1) {
newIndex = this.state.data[index].currentImageIndex + 1;
arr[index].cantGoPrev = true;
this.setState({data:arr})
}
if (newIndex === this.state.data[index].images.length - 1) {
newIndex = 0;
arr[index].cantGoNext = true;
this.setState({data:arr})
}
} else {
if (newIndex > 0) {
newIndex = this.state.data[index].currentImageIndex - 1;
arr[index].cantGoNext = true;
this.setState({data:arr})
}
if (newIndex === 0) {
arr[index].cantGoPrev = false;
this.setState({data:arr})
}
}
arr[index].currentImageIndex = newIndex;
this.setState({ data:arr });
}
render() {
return (
<div className="App">
<div>
<h3>Products</h3>
<div className="collection">
{this.state.data.map((item, index) => (
<div key={item.id} className="product">
<div className="product__image">
<div>
<button
disabled={!item.cantGoPrev}
data-direction="prev"
onClick={(e)=> this.nextSlideHandler(e,item, index)}
className="prev"
>
prev
</button>
</div>
<div>
<img
src={item.images[item.currentImageIndex]}
alt=""
/>
{item.images.currentImageIndex}
</div>
<div>
<button
disabled={!item.cantGoNext}
data-direction="next"
onClick={(e)=> this.nextSlideHandler(e, item, index)}
className="next"
>
next
</button>
</div>
</div>
<div className="product__name">{item.name}</div>
<div className="product__price">{item.price}</div>
</div>
))}
</div>
</div>
</div>
);
}
}
What is the best way to write a slider?
I will be glad of any help
First: There are many ways to achieve what you are trying to do, but this is how I would have done it.
Instead of using index to update different state, I would make a own component for Product. Inside that component you have full access to that products state and props. This makes it easier to work with the correct data.
I would also remove the next/previous-logic from the state and just do a check if the buttons should be active on render.
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
data: [
{
id: 1,
name: 'Product 1',
price: 50,
q: 0,
category: 'Sporting Goods',
images: [
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg'
],
currentImageIndex: 0,
isCycleMode: false
},
{
id: 2,
name: 'Product 2',
price: 70,
q: 0,
category: 'Sporting Goods',
images: [
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property04.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property02.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property01.jpg',
'https://ihatetomatoes.net/demos/_rw/01-real-estate/tn_property03.jpg'
],
currentImageIndex: 0,
isCycleMode: false
}
]
}
}
handleChange = (arr) => {
// State updates based on other state should be asynchronous
// https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
this.setState((state, props) => {
const oldArr = [...state.data]
const arrIndex = oldArr.findIndex(x => x.id === arr.id)
oldArr[arrIndex] = arr
return ({
data: oldArr
})
})
}
render () {
return (
<div className='App'>
<div>
<h3>Products</h3>
<div className='collection'>
{this.state.data.map((item) => (
<Product
item={item}
key={item.id}
onChange={this.handleChange}
/>
))}
</div>
</div>
</div>
)
}
}
class Product extends React.Component {
handleSlideChange = (e) => {
const arr = { ...this.props.item }
if (e.currentTarget.dataset.direction === 'next') {
arr.currentImageIndex++
} else {
arr.currentImageIndex--
}
this.props.onChange(arr)
};
render () {
const { item } = this.props
return (
<div key={item.id} className='product'>
<div className='product__image'>
<div>
<button
disabled={item.currentImageIndex <= 0}
data-direction='prev'
onClick={this.handleSlideChange}
className='prev'
>
Prev
</button>
</div>
<div>
<img src={item.images[item.currentImageIndex]} alt='' />
{item.images.currentImageIndex}
</div>
<div>
<button
disabled={item.currentImageIndex >= item.images.length - 1}
data-direction='next'
onClick={this.handleSlideChange}
className='next'
>
Next
</button>
</div>
</div>
<div className='product__name'>{item.name} {item.currentImageIndex}</div>
<div className='product__price'>{item.price}</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Categories