I am learning react and code a function for image slider and change image title with every slide change.
but I want to add any other animation on this text only and repeat that same animation after every slide change.
I don't know how to do it can anyone help me please.
I provide live working demo link for your batter understanding.
https://codesandbox.io/live/a1dd4cecd03
import React, {useEffect, useState} from 'react'
import './Slider.css'
import BtnSlider from './BtnSlider'
import DataSlider from './DataSlider'
export default function Slider() {
const [slideIndex, setSlideIndex] = useState(1)
const nextSlide = () => {
if(slideIndex !== DataSlider.length){
setSlideIndex(slideIndex + 1)
}
else if (slideIndex === DataSlider.length){
setSlideIndex(1)
}
}
const prevSlide = () => {
if(slideIndex !== 1){
setSlideIndex(slideIndex - 1)
}
else if (slideIndex === 1){
setSlideIndex(DataSlider.length)
}
}
useEffect(() => {
const slideInterval = setInterval(() => {
nextSlide()
}, 10000);
return () => clearInterval(slideInterval);
}, [slideIndex]);
const moveDot = index => {
setSlideIndex(index)
}
return (
<div className="container-slider">
{DataSlider.map((obj, index) => {
return (
<div className='bgco'>
<div className='overlay_shadow'></div>
<div key={obj.id} className={slideIndex === index + 1 ? "slide active-anim" : "slide"}>
<div className='slider_name'>
<span>{obj.title}</span>
</div>
<img src={process.env.PUBLIC_URL + `/images/img${index + 1}.jpg`}/>
</div>
</div>
)
})}
<BtnSlider moveSlide={nextSlide} direction={"next"} />
<BtnSlider moveSlide={prevSlide} direction={"prev"}/>
<div className="container-dots">
{Array.from({length: 5}).map((item, index) => (
<div
onClick={() => moveDot(index + 1)}
className={slideIndex === index + 1 ? "dot active" : "dot"}
></div>
))}
</div>
</div>
)
}
Related
I have a Next app with a carousel on my home page. I am trying to test with Playwright. I want to get the initial alt text, click right to navigate, and then compare the current alt text to the initial alt text to verify the image did change. I have tried all manner of comparing the alt text to include asserting the initial alt text after the move and have not been successful so far.
This is the component:
import React, { useState } from "react";
import { useSwipeable } from "react-swipeable";
import Image from "next/image";
import Link from "next/link";
import Button from "../button/button";
import useWindowSize from "../../hooks/useWindowSize";
import { BsDot, BsChevronRight, BsChevronLeft } from "react-icons/bs";
import styles from "./carousel.module.css";
export default function Carousel(data) {
const [selectedCarousel, setSelectedCarousel] = useState(0);
const carouselData = data.data;
const deviceType = useWindowSize();
const handlers = useSwipeable({
onSwipedLeft: () => scrollCarousel("right"),
onSwipedRight: () => scrollCarousel("left"),
});
const toggleCarousel = (id) => {
setSelectedCarousel(id);
};
const scrollCarousel = (direction) => {
if (direction == "right" && selectedCarousel < carouselData.length - 1) {
setSelectedCarousel(selectedCarousel + 1);
} else if (direction == "left" && selectedCarousel > 0) {
setSelectedCarousel(selectedCarousel - 1);
} else if (direction == "right") {
setSelectedCarousel(0);
} else if (direction == "left") {
setSelectedCarousel(carouselData.length - 1);
}
};
return (
<>
<div className={styles.container} {...handlers} data-testid="carousel" id="carousel">
<Image
src={carouselData[selectedCarousel].imgUrl}
alt={carouselData[selectedCarousel].altText}
width="600px"
height="300px"
layout="responsive"
loading="lazy"
/>
{deviceType === "computer" ? (
<>
<BsChevronLeft
className={styles.left}
onClick={() => scrollCarousel("left")}
/>
<BsChevronRight
className={styles.right}
onClick={() => scrollCarousel("right")}
/>
</>
) : null}
<div className={styles.carouselBtn}>
<Button
text={carouselData[selectedCarousel].buttonText}
url={carouselData[selectedCarousel].buttonLink}
/>
</div>
</div>
<div className={styles.selector}>
{carouselData.map((data) => (
<span key={data.id}>
<BsDot
className={`${styles.dot} ${
selectedCarousel === data.id ? styles.selectedDot : null
}`}
onClick={() => toggleCarousel(data.id)}
/>
</span>
))}
</div>
</>
);
}
Here is my most recent attempt
test.describe("Mobile Homepage", () => {
test.use({ viewport: { width: 390, height: 844}})
test("has swipeable carousel", async ({page}) => {
await page.goto('http://localhost:3000/')
await expect(page.getByAltText("Test alt text 1")).toBeVisible()
const center = { x: 195, y: 200}
await page.mouse.move(center.x + 50,center.y)
await page.mouse.down();
await page.mouse.move(center.x - 50,center.y)
await page.mouse.up()
await expect(page.getByAltText("Test alt text 3")).toBeVisible()
})
I'm attempting to use collapsible within a React stateless component. With this, I was able to write the following code:
import React, { useState } from 'react';
import data from '../home/db/settingsData';
const collapsible_function = () => {
const collapses = document.getElementsByClassName("settings--collapsible");
for (let i = 0; i < collapses.length; i++) {
const collapse = collapses[i];
collapse.addEventListener("click", () => {
collapse.classList.toggle("active");
var content = collapse.nextElementSibling;
content.style.maxHeight = content.style.maxHeight ? null : content.scrollHeight + "px";
})
}
}
const settings = (user_role) => data.map(d => {
return (d.role === user_role ? d.settings.map(setting => {
return <div className='settings' key={setting.id} id={setting.id}>
<div className='settings--sub--options profile--settings'>
<button className="settings--collapsible" onClick={collapsible_function}>{ setting.title }</button>
<ul className="setting--content">
{
setting.list_item.map((item, index) => {
return (<li key={index} id={index}><a href='http'>{item}</a></li>)
})
}
</ul>
</div>
</div>
}) : "");
})
const useSentenceCase = (user_role) => user_role[0].toUpperCase() + user_role.substring(1, user_role.length + 1);
function Settings(props) {
const [role] = useState(props.role);
return (
<div className='settings--bar'>
<h4 className='setting--title'>{ useSentenceCase(role) } | Settings</h4>
{ settings(role) }
</div>
)
}
export default Settings;
Note: The above code is working fine but the click only response after the second click.
Would you help out of this issue?
The easiest way to migrate your code over to something that addresses your issue is to move your event-handler declaration to a component side effect:
import React, { useState, useEffect } from 'react';
import data from '../home/db/settingsData';
const settings = (user_role) =>
data.map((d) => {
return d.role === user_role
? d.settings.map((setting) => {
return (
<div
className='settings'
key={setting.id}
id={setting.id}
>
<div className='settings--sub--options profile--settings'>
<button className='settings--collapsible'>
{setting.title}
</button>
<ul className='setting--content'>
{setting.list_item.map((item, index) => {
return (
<li
key={index}
id={index}
>
<a href='http'>{item}</a>
</li>
);
})}
</ul>
</div>
</div>
);
})
: '';
});
const useSentenceCase = (user_role) =>
user_role[0].toUpperCase() + user_role.substring(1, user_role.length + 1);
function Settings(props) {
useEffect(() => {
const collapses = document.getElementsByClassName('settings--collapsible');
for (const collapse of collapses) {
collapse.addEventListener('click', () => {
collapse.classList.toggle('active');
const content = collapse.nextElementSibling;
content.style.maxHeight = content.style.maxHeight
? null
: content.scrollHeight + 'px';
});
}
});
const [role] = useState(props.role);
return (
<div className='settings--bar'>
<h4 className='setting--title'>{useSentenceCase(role)} | Settings</h4>
{settings(role)}
</div>
);
}
export default Settings;
This should create an event handler on component mount and subsequent re-renders, then the first click of the .settings--collapsible button should result in the functionality you want.
Hope this helps.
am making a slider, when i click on an item to slide to the second item, the screen flashs when the function is called ( the func that changes the items )
This comes exactly when clickAction(); is called.
Here is the demo : https://gfycat.com/ultimatetiredequestrian
PS: the click is handled in another file, here is some code :
const VerticalCarousel = ({ clickActionTransfer }) => {
.
..
...
<button
data-glitch={item.type}
type="button"
onClick={clickActionTransfer}
key={i}>
CLICK
{item.type}
</button>
};
This is my <SLider/> :
import React, { useState, useEffect } from "react";
import VerticalCarousel from "./carousel/VerticalCarousel";
const Slider = ({ getData, idd, clickAction, children }) => {
const [dataSlider, setDataSlider] = useState([]);
const [displayTitleState, setDisplayTitleState] = useState(true);
const [displayCarouselState, setDisplayCarouselState] = useState(true);
const [currentIndex, setCurrentIndex] = useState(0);
const [length, setLength] = useState(children.length);
const handleSlideClick = () => {
if (currentIndex < length - 1) {
setCurrentIndex((prevState) => prevState + 1);
}
setDisplayCarouselState(false);
setTimeout(() => {
setDisplayTitleState(false);
clickAction();
}, 1000);
setTimeout(() => {
setDisplayTitleState(true);
setDisplayCarouselState(true);
}, 2600);
};
useEffect(() => {
setLength(children.length);
setDataSlider(getData);
}, [getData, children]);
return (
<div className="slider__container">
{dataSlider.map((i) => {
return (
idd === i.id && (
<div key={i.id}>
<div
style={{
transform: `translateX(-${currentIndex * 100}%)`,
display: "flex",
transition: "all .5s linear",
}}
>
{" "}
{children}{" "}
</div>
<div className="slider__content">
<div
className={
"slider__left " + (i.theme === "light" ? "light-theme" : "")
}
>
<div className="slider__progressBar">
{" "}
{i.progressBar.nb1}{" "}
<span className="slider__progressBar-lightTxt">
{" "}
{i.progressBar.nb1}
</span>{" "}
{i.progressBar.text}
</div>
<h1 className={displayTitleState ? "showTitle" : "hideTitle"}>
{" "}
{i.title}{" "}
</h1>
</div>
<div
className={`slider__right ${
displayCarouselState ? "showCarousel" : "hideCarousel"
} ${i.theme === "light" ? "light-theme" : ""}`}
>
<VerticalCarousel
data={i.slider}
clickActionTransfer={handleSlideClick}
/>
</div>
</div>
</div>
)
);
})}
</div>
);
};
export default Slider;
App.js :
import React, { useState, useEffect } from "react";
import Slider from "./Slider";
import "./App.scss";
function App() {
const [loadSlide, setLoadSlide] = useState(0);
const [data, setData] = useState([]);
const handleclick = () => {
setLoadSlide(loadSlide + 1);
};
useEffect(() => {
setLoadSlide((loadSlide) => loadSlide + 1);
fetch("/data.json")
.then((response) => response.json())
.then((json) => {
setData(json.lvmh.sliders);
});
}, []);
return (
<div className="App">
<div
className={
"slider " +
(loadSlide >= 1 &&
loadSlide <= data.length &&
loadSlide + 1 &&
`ifSlider`)
}
>
<>
{loadSlide >= 0 && loadSlide <= data.length && loadSlide + 1 && (
<Slider clickAction={handleclick} idd={loadSlide} getData={data}>
{data.map((img) => {
return (
<img className={"slider__bg---- "} src={img.img} alt="" />
);
})}
</Slider>
)}
</>
</div>
</div>
);
}
export default App;
I found it, i don't know why but the bug comes from the browser console when it's opened ! weird !
I'm new to React / JS and I've made a slideshow that advances slides by clicking the dots underneath. I would like to add support for swiping on the slideshow on the mobile device because the dots are too small. I've seen some tutorials for this, but they don't seem to play nice with the one I used to make the slideshow. Is this possible using my current setup? How is the most straightforward way to go about this?
import React from 'react';
import './Slideshow.css';
import rmsm1 from './img/RMSMobile1.png';
import rmsm2 from './img/RMSMobile2.png';
import rmsm3 from './img/RMSMobile3.png';
const Slides = [rmsm1, rmsm2, rmsm3];
const delay = 15000;
function RMSMobileSlideshow() {
const [index, setIndex] = React.useState(0);
const timeoutRef = React.useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
React.useEffect(() => {resetTimeout();
timeoutRef.current = setTimeout(
() =>
setIndex((prevIndex) =>
prevIndex === Slides.length - 1 ? 0 : prevIndex + 1
),
delay
);
return () => {resetTimeout();};
}, [index]);
return (
<div className="slideshow">
<div className="slideshowSlider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }} >
{Slides.map((imageSource, index) => (
<img
className="slide"
key={index}
src={imageSource}
alt="Slide"
/>
))}
</div>
<div className="slideshowDots">
{Slides.map((_, idx) => (
<div key={idx} className={`slideshowDot${index === idx ? " active" : ""}`} onClick={() => {
setIndex(idx);
}}></div>
))}
</div>
</div>
);
}
export default RMSMobileSlideshow;
I figured out how to do this- I added this code here:
import './Slideshow.css';
import rmsm1 from './img/RMSMobile1.png';
import rmsm2 from './img/RMSMobile2.png';
import rmsm3 from './img/RMSMobile3.png';
import { useState } from "react";
const Slides = [rmsm1, rmsm2, rmsm3];
const delay = 15000;
function RMSMobileSlideshow() {
const [index, setIndex] = React.useState(0);
const timeoutRef = React.useRef(null);
New code starts here:
const [touchPosition, setTouchPosition] = useState(null)
const handleTouchStart = (e) => {
const touchDown = e.touches[0].clientX
setTouchPosition(touchDown)
}
const handleTouchMove = (e) => {
const touchDown = touchPosition
if(touchDown === null) {
return
}
const currentTouch = e.touches[0].clientX
const diff = touchDown - currentTouch
if (diff > 5) {
setIndex((prevIndex) =>
prevIndex === Slides.length - 1 ? 0 : prevIndex + 1
)
}
if (diff < -5) {
setIndex((prevIndex) =>
prevIndex === Slides.length - 1 ? 0 : prevIndex - 1
)
}
setTouchPosition(null)
}
And then I added onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} to the main container like so:
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
React.useEffect(() => {resetTimeout();
timeoutRef.current = setTimeout(
() =>
setIndex((prevIndex) =>
prevIndex === Slides.length - 1 ? 0 : prevIndex + 1
),
delay
);
return () => {resetTimeout();};
}, [index]);
return (
<div className="slideshow" onTouchStart={handleTouchStart} onTouchMove={handleTouchMove}>
<div className="slideshowSlider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }} >
{Slides.map((imageSource, index) => (
<img
className="slide"
key={index}
src={imageSource}
alt="Slide"
/>
))}
</div>
<div className="slideshowDots">
{Slides.map((_, idx) => (
<div key={idx} className={`slideshowDot${index === idx ? " active" : ""}`} onClick={() => {
setIndex(idx);
}}></div>
))}
</div>
</div>
);
}
export default RMSMobileSlideshow;
I'm building a game where you have to click on a cross pictogram or check pictogram depending on whether the answer were correct or false. But as soons as you have answered you can't click again on none of the two pictograms (even on the one you didn't click on).
In order to do this, I used useRef on the element that contains the pictograms and I tried to make the element disabled. It didn't work.
I've also tried to make their parent element "uneventable" but it didn't work neither
I would appreciate any help. thank you
const pictoAnswer = useRef()
const divParent = useRef()
pictoAnswer.current.disabled = true
divParent.current.pointerEvents = "none"
overall view
import React, { useState, useRef } from "react";
// modules
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCheck, faTimes } from "#fortawesome/free-solid-svg-icons";
const WordList = () => {
const liBgColor = useRef();
const pictoAnswer = useRef();
const divParent = useRef();
// change the color bg of the list element
const isCorrect = (answer) => {
if (answer) {
liBgColor.current.style.backgroundColor = "#5DCF36";
} else {
liBgColor.current.style.backgroundColor = "#d15f5f";
}
};
//list of word to make guess
let wordsToGuess = ["chambre", "salon", "rire", "fusée"];
const numberOfWordToGuess = wordsToGuess.length;
const [indexWord, setIndexWord] = useState(1);
console.log("indexWord:", indexWord);
const [wordToDisplay, setWordToDisplay] = useState([wordsToGuess[0]]);
// push the next word to guess into the wordToDisplay array
const handleAnswer = (answer) => {
pictoAnswer.current.disabled = true;
divParent.current.pointerEvents = "none";
//set the new index of the word item
setIndexWord(indexWord + 1);
//change the bgColor depending on the answer
isCorrect(answer);
//would display the next word or not
if (indexWord == numberOfWordToGuess || indexWord > numberOfWordToGuess) {
console.log("no more word to guess");
} else {
setWordToDisplay([...wordToDisplay, wordsToGuess[indexWord]]);
}
};
return (
<ul className="word__list">
{wordToDisplay.map((word, i) => (
<li key={i} className="word__item" ref={liBgColor}>
<p className="word">{word}</p>
<div className="icon-box" ref={divParent}>
<span
ref={pictoAnswer}
onClick={() => {
handleAnswer(false);
}}
>
<FontAwesomeIcon icon={faTimes} color={"#FF5252"} />
</span>
<span
ref={pictoAnswer}
onClick={() => {
handleAnswer(true);
}}
>
<FontAwesomeIcon icon={faCheck} color={"#008000"} />
</span>
</div>
</li>
))}
</ul>
);
};
export default WordList;
codeSandBox
The main issue with your code is incorrect use of React refs to manipulate DOM elements.
Solution
Store a "settled" state array initialized to null values to indicate if a word has been marked true/false. Same condition is used to prevent further "click" handling.
Use CSS to style the list item.
Update handleAnswer to set the "settled" state and increment the word index.
Use an useEffect hook to update the words to display.
Code:
const wordsToGuess = ["chambre", "salon", "rire", "fusée"];
const App = () => {
const [indexWord, setIndexWord] = useState(0);
const [wordToDisplay, setWordToDisplay] = useState([]);
const [settled, setSettled] = useState(Array(wordsToGuess.length).fill(null));
useEffect(() => {
setWordToDisplay(wordsToGuess.slice(0, indexWord + 1));
}, [indexWord]);
const handleAnswer = (index, answer) => () => {
if (settled[index] === null) {
setSettled((settled) =>
settled.map((el, i) => (i === indexWord ? answer : el))
);
if (indexWord < wordsToGuess.length - 1) setIndexWord((i) => i + 1);
}
};
return (
<ul className="word__list">
{wordToDisplay.map((word, i) => (
<li
key={i}
className={classNames("word__item", {
"settled-true": settled[i] === true,
"settled-false": settled[i] === false
})}
>
<p className="word">{word}</p>
<div className="icon-box">
<span onClick={handleAnswer(i, false)}>
<FontAwesomeIcon icon={faTimes} color={"#FF5252"} />
</span>
<span onClick={handleAnswer(i, true)}>
<FontAwesomeIcon icon={faCheck} color={"#008000"} />
</span>
</div>
</li>
))}
</ul>
);
};
CSS:
.settled-true {
background-color: #5dcf36;
}
.settled-false {
background-color: #d15f5f;
}
Demo