The loop works only once and then nothing happens. I have three testimonials, and can go only once forward or backwords.Thanks for help!
const nextBtn = document.querySelector(".next-btn");
const prevBtn = document.querySelector(".prev-btn");
const testimonials = document.querySelectorAll(".testimonial");
let index = 0;
window.addEventListener("DOMContentLoaded", function () {
show(index);
});
function show(index) {
testimonials.forEach((testimonial) => {
testimonial.style.display = "none";
});
testimonials[index].style.display = "flex";
}
nextBtn.addEventListener("click", function () {
index++;
if (index > testimonials.length - 1) {
index = 0;
}
show(index);
});
prevBtn.addEventListener("click", function () {
index--;
if (index < 0) {
index = testimonials.length - 1;
}
show(index);
});
I would use a "hidden" class to hide the non-active testimonials instead of manipulating the element's style in-line. Also, your navigation logic can be simplified to a modulo operation.
The code your originally posted seemed to work out well, but it seems to cluttered with redundancy (code reuse). It also lacks structural flow (readability).
const
modulo = (n, m) => (m + n) % m,
moduloWithOffset = (n, m, o) => modulo(n + o, m);
const
nextBtn = document.querySelector('.next-btn'),
prevBtn = document.querySelector('.prev-btn'),
testimonials = document.querySelectorAll('.testimonial');
let index = 0;
const show = (index) => {
testimonials.forEach((testimonial, currIndex) => {
testimonial.classList.toggle('hidden', currIndex !== index)
});
}
const navigate = (amount) => {
index = moduloWithOffset(index, testimonials.length, amount);
show(index);
}
// Create handlers
const onLoad = (e) => show(index);
const onPrevClick = (e) => navigate(-1);
const onNextClick = (e) => navigate(1);
// Add handlers
window.addEventListener('DOMContentLoaded', onLoad);
nextBtn.addEventListener('click', onNextClick);
prevBtn.addEventListener('click', onPrevClick);
.testimonial {
display: flex;
}
.testimonial.hidden {
display: none;
}
<div>
<button class="prev-btn">Prev</button>
<button class="next-btn">Next</button>
</div>
<div>
<div class="testimonial">A</div>
<div class="testimonial">B</div>
<div class="testimonial">C</div>
<div class="testimonial">D</div>
<div class="testimonial">E</div>
<div class="testimonial">F</div>
</div>
Related
For example, I want to display the number 90,000 while it is counting up.
I have already managed to count up with the following code, but without "." or ",":
<div class="fact">
<div class="number" num="90000">0</div>
</div>
const counters = document.querySelectorAll('.number');
const speed = 500;
counters.forEach( counter => {
const animate = () => {
const value = +counter.getAttribute('num');
const data = +counter.innerText;
const time = value / speed;
if(data < value) {
counter.innerText = Math.ceil(data + time);
setTimeout(animate, 1);
}else{
counter.innerText = value;
}
}
animate();
});
Have already found examples with thousands separator but I can't manage to convert it to my project.
while (/(\d+)(\d{3})/.test(val.toString())){
val = val.toString().replace(/(\d+)(\d{3})/, '$1'+','+'$2');
I have little experience with javascript.
Per my comment, Keep data separate from formatting
const counters = document.querySelectorAll('.number');
const speed = 500;
const formatter = new Intl.NumberFormat();
counters.forEach(counter => {
const animate = () => {
let value = +counter.dataset.num;
let data = +counter.dataset.state;
let time = value / speed;
if (data < value) {
counter.dataset.state = Math.ceil(data + time);
setTimeout(animate, 1);
}
counter.innerText = formatter.format(counter.dataset.state);
}
animate();
});
<div class="fact">
<div class="number" data-num="90000" data-state="0">0</div>
<div class="number" data-num="45000" data-state="0">0</div>
</div>
This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 2 years ago.
import React, {useState} from 'react';
function Slides({slides}) {
const [slideCount, setSlideCount] = useState(0);
const [slide, setSlide] = useState(slides[0]);
const [restartDisable, setRestartDisable] = useState(true);
const [previousDisable, setPreviousDisable] = useState(true);
const [nextDisable, setNextDisable] = useState(false);
const restartClick = () => {
setRestartDisable(true);
setPreviousDisable(true);
setNextDisable(false);
setSlide(slides[0]);
setSlideCount(0);
console.log("restartCLick ",slideCount);
}
const previousClick = () => {
setSlideCount(prevCount => prevCount - 1);
if (slideCount === 0) {
setRestartDisable(true);
setPreviousDisable(true);
setNextDisable(false);
setSlide(slides[0]);
} else {
setSlide(slides[slideCount]);
}
console.log("previousCLick ",slideCount);
}
const nextClick = () => {
let newSlideCount = slideCount
newSlideCount++
console.log(newSlideCount)
setSlideCount(newSlideCount);
if (slideCount === (slides.length - 1)) {
setNextDisable(false);
setSlideCount(prevCount => prevCount + 1);
setSlide(slides[slideCount]);
} else {
setRestartDisable(false);
setPreviousDisable(false);
setSlide(slides[slideCount]);
}
console.log("nextCLick ",slideCount);
}
return (
<div>
<div id="navigation" className="text-center">
<button data-testid="button-restart" className="small outlined" disabled={restartDisable} onClick={()=>restartClick()}>Restart</button>
<button data-testid="button-prev" className="small" disabled={previousDisable} onClick={()=>previousClick()}>Prev</button>
<button data-testid="button-next" className="small" disabled={nextDisable} onClick={()=>nextClick()}>Next</button>
</div>
<div id="slide" className="card text-center">
<h1 data-testid="title">{slide.title}</h1>
<p data-testid="text">{slide.text}</p>
</div>
</div>
);
}
export default Slides;
The setSlideCount() is not setting the slideCount as expected, its incrementing late.
Whenever I click nextSlide the increment is shown in the react developer tools but the value remains same of the slideCount. Same thing applies for previousClick button also. But for restart button it works properly in setting to 0 but for the next button clicks and previous button clicks the slideCount value is not updating as expected, please help in setting slideCount value.
That's because setState is an asynchronous operation and won't update the state immidiately.
Here is the changes you need to make in your funtions:
const previousClick = () => {
setSlideCount((prevCount) => {
const newSlideCount = prevCount - 1;
if (newSlideCount === 0) {
setRestartDisable(true);
setPreviousDisable(true);
setNextDisable(false);
setSlide(slides[0]);
} else {
setSlide(slides[newSlideCount]);
}
console.log("previousCLick ", newSlideCount);
return newSlideCount;
});
};
const nextClick = () => {
setSlideCount((prevValue) => {
const newSlideCount = prevValue + 1;
if (newSlideCount === slides.length - 1) {
setNextDisable(false);
setSlideCount((prevCount) => prevCount + 1);
setSlide(slides[newSlideCount]);
} else {
setRestartDisable(false);
setPreviousDisable(false);
setSlide(slides[newSlideCount]);
}
console.log("nextCLick ", newSlideCount);
return newSlideCount;
});
};
After reading the title, you might have known the problem. Let me elaborate: when I add an element using JavaScript, I can't do anything with that element. When the element is clicked, the element is supposed to do a certain function, but when I add the new element, just does nothing.
Code:
<div class="progress-bar">
<div class="progress-bar-title">Progress:</div>
<div class="progress-bar-outline">
<div class="progress-bar-percentage"></div>
</div>
</div>
<div class="list" id="listSection">
<ul>
<li>one</li>
<li>two</li>
<li>test</li>
</ul>
</div>
<div class="new-button">Create</div>
<div class="new-section">
<input type="text" class="text-box" placeholder="Name for this card">
</div>
//creates a new element
newText.addEventListener("keyup", (event) => {
if (newText.value != "") {
if (event.keyCode === 13) {
let list_section = document.getElementById("listSection");
let name = newText.value;
let new_li = document.createElement("li");
new_li.innerHTML = name;
list_section.querySelector("ul").appendChild(new_li);
let divs = document.querySelectorAll("div");
newSection.classList.remove("opened");
divs.forEach((div) => {
if (div != newSection) {
div.style.transition = "all 0.5s ease";
div.style.filter = "";
}
});
}
}
});
//looping through each list to add that function
list_Sections.forEach((list) => {
totalListCount++;
list.addEventListener("click", () => {
list.classList.toggle("checked"); //this function doesn't apply to the newly created element
if (!list.classList.contains("checked")) {
listCompleted--;
} else {
listCompleted++;
}
average = (listCompleted / totalListCount) * 500;
percentage.style.width = average;
});
});
Ask you have any questions about this topic.
Thanks for the help!
Whenever a new element is added, your code should attach the event listener to that new element. Try nesting your the code related to .addEventListner() inside the keyup event listener code.
newText.addEventListener("keyup", (event) => {
if (newText.value != "") {
if (event.keyCode === 13) {
let list_section = document.getElementById("listSection");
let name = newText.value;
let new_li = document.createElement("li");
new_li.innerHTML = name;
list_section.querySelector("ul").appendChild(new_li);
let divs = document.querySelectorAll("div");
newSection.classList.remove("opened");
divs.forEach((div) => {
if (div != newSection) {
div.style.transition = "all 0.5s ease";
div.style.filter = "";
}
});
//looping through each list to add that function
list_Sections.forEach((list) => {
totalListCount++;
list.addEventListener("click", () => {
list.classList.toggle("checked");
if (!list.classList.contains("checked")) {
listCompleted--;
} else {
listCompleted++;
}
average = (listCompleted / totalListCount) * 500;
percentage.style.width = average;
});
});
}
}
});
I have a typewrite effect function. Now I want to show the code (the effect) in the div (typingRef), I made something like typingRef.current = letter but is that the same as document.querSelector('typing').textContent = letter? I want to work with the ref not the queryselector
export function Text({ children }) {
const headingRef = React.useRef(null)
const typingRef = React.useRef(null)
const texts = ['websites', 'applications', 'mobile' ]
React.useEffect(() => {
let count = 0
let index = 0
let currentText = ''
let letter = ''
function type() {
if (count === texts.length) {
count = 0
}
currentText = texts[count]
letter = currentText.slice(0, ++index)
typingRef.current = letter
if (letter.length === currentText.length) {
count++
index = 0
}
setTimeout(type, 400)
}
type()
})
return (
<div className={styles.component}>
<div className={styles.intro}>
<h1 ref={headingRef} className={styles.heading}>
{ children }
</h1>
<div ref={typingRef} className={styles.typing} />
</div>
</div>
)
}
Best I can come up with from trying to understand your question. Try this: typingRef.current.innerHTML
export const Text = ({ children }) => {
const headingRef = React.useRef(null)
const typingRef = React.useRef(null)
const texts = ['websites', 'applications', 'mobile' ]
React.useEffect(() => {
let count = 0
let index = 0
let currentText = ''
let letter = ''
function type() {
if (count === texts.length) {
count = 0
}
currentText = texts[count]
letter = currentText.slice(0, ++index)
typingRef.current.innerHTML = letter
if (letter.length === currentText.length) {
count++
index = 0
}
setTimeout(type, 400)
}
type()
})
return (
<div>
<div>
<h1 ref={headingRef}>
{ children }
</h1>
<div ref={typingRef}/>
</div>
</div>
)
}
I was able to make this work, but I believe I made it really dirty work here. I was wondering, is there a simpler way to do this. I'm asking for learning purposes.
Fetch is pulling json objects and the objects have an image array in them, then populates the content inside their own containers with the images. I tried to make it switch between the images with prev and next buttons.
Every container holds 4 or 5 different images.
Here is my code. I tried to stick with ES6. I don't want to use jquery on this one.
fetch('./public/js/projects.json')
.then((Response) => {
return Response.json();
}).then((data) => {
const projects = data.projects;
projects.map((project, index) => {
const container_projects = document.getElementById('projects');
const project_img = project.img
const markup_project = `
<div class="project" aria-label="${index}">
<div class="project_image">
<div class="arrow_container">
<button class="arrows left_arrow"><img src="assets/svg/left_arrow.svg" class="left"/></button>
<div class="numbers">
<span class="current">1</span>
<span class="divider">/</span>
<span class="total">${project_img.length}</span>
</div>
<button class="arrows right_arrow"><img src="assets/svg/right_arrow.svg" class="right"/></button>
</div>
<div class="image_container">
${project_img.map(image => `<img src="${image}" class=""/>`).join('')}
</div>
</div>
<small class="project_name">${project.title}</small>
</div>
`
container_projects.innerHTML += markup_project;
})
return projects
}).then((data) => {
data.map((project, index) => {
const current_project = document.querySelectorAll(`.project`);
const images = [].slice.call(current_project[index].querySelectorAll('.image_container img'));
const arrows = current_project[index].querySelectorAll('.arrows');
const current = current_project[index].querySelector('.current');
images[0].classList.add('active');
arrows.forEach((arrow) => {
arrow.addEventListener('click', (e) => {
let num;
images.map((image, index)=> {
if (image.classList.contains('active')){
image.classList.remove('active');
if (e.target.className == 'right'){
num = index + 1
} else {
num = index - 1
}
if (num >= images.length){
num = 0;
}
else if (num <= -1 ){
num = images.length - 1
}
console.log(num)
return num;
}
return num;
})
images[num].classList.add('active');
current.innerHTML = num + 1;
});
})
})
}).catch((error) => {
console.log(error);
})
Thanks for your help.