I have been working on a project and while trying to create a slideshow I found somewhere, wanted to edit it so that instead of using the setTimeout to scroll through the images or the circles, I want to be able to use the buttons to go left and right but just cant seem to figure it out.
Here is the link to what it looks like:
https://codesandbox.io/s/throbbing-bash-ir9g0?fontsize=14&hidenavigation=1&theme=dark
Anyone able to point me in the right direction?
In order to achieve what you are looking for, we just need to add click handlers for left and right buttons.
Below are the respective functions in order to display the next/previous slide based on the current index.
//Handler to update the current index on click of right button
const onNextClick = () => {
if(index !== colors.length - 1) {
setIndex(idx => idx + 1);
}
}
//Handler to update the current index on click of left button
const onPreviousClick = () => {
if(index !== 0) {
setIndex(idx => idx - 1);
}
}
<button className="left" onClick={onPreviousClick}>‹</button>
<button className="right" onClick={onNextClick}>›</button>
Adding these functions to slideshow.js & updating the buttons as shown will help you in achieving the expected functionality.
Here is the demo
const colors = ["#0088fE", "#00C49F", "#FFBB28"];
const delay = 5000;
function Slideshow() {
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 === colors.length - 1 ? 0 : prevIndex + 1
),
delay
);
return () => {
resetTimeout();
};
}, [index]);
//Handler to update the current index on click of right button
const onNextClick = () => {
if(index !== colors.length - 1) {
setIndex(idx => idx + 1);
}
}
//Handler to update the current index on click of left button
const onPreviousClick = () => {
if(index !== 0) {
setIndex(idx => idx - 1);
}
}
return (
<div className="slideshow">
<div
className="slideshow-slider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
>
{colors.map((backgroundColor, index) => (
<div className="slide" key={index} style={{ backgroundColor }} />
))}
</div>
<button className="left" onClick={onPreviousClick}>‹</button>
<button className="right" onClick={onNextClick}>›</button>
<div className="slideshow-tabs">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshow-tab${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
}
ReactDOM.render(<Slideshow />, document.getElementById("react"));
.slideshow {
margin: 0 auto;
overflow: hidden;
width: 30%;
position: relative;
}
.slideshow-slider {
white-space: nowrap;transition: ease 1000ms;
}
.slide {
display: inline-block;
height: 240px;
width: 100%;
}
button {
width: 25px;
height: 240px;
position: absolute;
cursor: pointer;
}
.left {
left: 0;
top: 0;
}
.right {
right: 0;
top: 0;
}
.slideshow-tabs {
text-align: center;
position: relative;
bottom: 50px;
}
.slideshow-tab {
display: inline-block;
height: 20px;
width: 20px;
border-radius: 50%;
cursor: pointer;
margin: 15px 7px 0px;
background-color: #c4c4c4;
}
.active {
background-color: #6a0dad;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
-- Update --
As per you requirement, if you wish to go back to the beginning at the end of all the slides, you can make the changes to onNextClick like below
const onNextClick = () => {
setIndex(idx => idx !== colors.length - 1? idx + 1: 0);
}
you can do that by handling the onClick event
here's the link of the code you could check this demo
import React from "react";
import "./slideshow.scss";
const colors = ["#0088fE", "#00C49F", "#FFBB28"];
function Slideshow() {
const [index, setIndex] = React.useState(0);
const handleChange=()=>{
setIndex((prevIndex) =>
prevIndex === colors.length - 1 ? 0 : prevIndex + 1
)
}
React.useEffect(() => {
}, [index]);
return (
<div className="slideshow">
<div
className="slideshow-slider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
>
{colors.map((backgroundColor, index) => (
<div className="slide" key={index} style={{ backgroundColor }} />
))}
</div>
<button className="left" onClick={()=>handleChange} >‹</button>
<button className="right" onClick={()=>handleChange}>›</button>
<div className="slideshow-tabs">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshow-tab${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
}
export default Slideshow;
Related
I'm creating a mobile navigation menu and using CSSTransition component from React Transition Group to handle animating in the different levels of my navigation.
I am able to successfully animate in different levels but only in one direction. For instance, the content will enter in from the right and exit to the left. The part that is confusing to me is when i need to change the direction that the content animates in or out.
For my example lets say I have 3 slides
Initially slide #2 would enter in from the right If we go to slide #3 it would exit left.
If we are on slide #3 and want to go back to slide #2 then I want slide #2 to enter in from the right.
import { useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import './App.css';
const App = () => {
const [page, setPage] = useState(1);
return (
<>
Page: {page}
<nav>
<button onClick={() => page !== 1 && setPage(page - 1)}>Prev</button>
<button onClick={() => page >= 1 && page <= 2 && setPage(page + 1)}>
Next
</button>
</nav>
<div className='content'>
<CSSTransition
in={page === 1}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideOne />
</CSSTransition>
<CSSTransition
in={page === 2}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideTwo />
</CSSTransition>
<CSSTransition
in={page === 3}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideThree />
</CSSTransition>
</div>
</>
);
};
const SlideOne = () => {
return <h3>Hello From Slide One</h3>;
};
const SlideTwo = () => {
return <h3>Hello From Slide Two</h3>;
};
const SlideThree = () => {
return <h3>Hello From Slide Three</h3>;
};
export default App;
.content {
width: 200px;
height: 100px;
overflow: hidden;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
padding: 1rem;
position: relative;
}
h3 {
position: absolute;
}
.slide-enter {
opacity: 0;
transform: translateX(100%);
}
.slide-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 0.5s;
}
.slide-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: all 0.5s;
}
import { useState } from "react";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import "./App.css";
const App = () => {
const [page, setPage] = useState(1);
const [direction, setDirection] = useState("left");
return (
<>
Page: {page}
<nav>
<button
onClick={() => {
page !== 1 && setPage(page - 1);
direction !== "right" && setDirection("right");
}}
>
{" "}
Prev
</button>
<button
onClick={() => {
page >= 1 && page <= 2 && setPage(page + 1);
direction !== "left" && setDirection("left");
}}
>
Next
</button>
</nav>
<div className="content">
{/* <TransitionGroup> */}
<CSSTransition
in={page === 1}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideOne />
</CSSTransition>
<CSSTransition
in={page === 2}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideTwo />
</CSSTransition>
<CSSTransition
in={page === 3}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideThree />
</CSSTransition>
{/* </TransitionGroup> */}
</div>
</>
);
};
const SlideOne = () => {
return <h3>Hello From Slide One</h3>;
};
const SlideTwo = () => {
return <h3>Hello From Slide Two</h3>;
};
const SlideThree = () => {
return <h3>Hello From Slide Three</h3>;
};
export default App;
.content {
width: 200px;
height: 100px;
overflow: hidden;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
padding: 1rem;
position: relative;
}
h3 {
position: absolute;
}
.slide-left-enter {
opacity: 0;
transform: translateX(100%);
}
.slide-left-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 2000ms;
}
.slide-left-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-left-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: all 2000ms;
}
.slide-right-enter {
opacity: 0;
transform: translateX(-100%);
}
.slide-right-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 2000ms;
}
.slide-right-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-right-exit-active {
opacity: 0;
transform: translateX(100%);
transition: all 2000ms;
}
the trick is to reverse the translation on every key button
In my react app, using React-Bootstrap, I set the navbar to fixed after srcolling, but after that I'm unable to use the toggler button, that was working before scrolling :
const [sticky, setSticky] = useState('');
React.useEffect(() => {
window.addEventListener('scroll', stickNavbar);
return () => window.removeEventListener('scroll', stickNavbar);
}, []);
const stickNavbar = () => {
if (window !== undefined) {
let windowHeight = window.scrollY;
// window height changed for the demo
windowHeight > 150 ? setSticky('top') : setSticky('');
}
};
return (
<Navbar bg="light" fixed={stickyClass} className="menu">
<Container fluid >
</Container>
</Navbar>
<Collapse in={open} timeout={200}>
<Container fluid className="dropdown-container">
<Row className="dropdown">
</Row>
</Container>
</Collapse>
)
CSS
.menu {
max-width: 100%;
height: 7rem;
border-bottom: 2px solid rgb(228, 228, 228);
}
.dropdown-container {
position: absolute;
}
.dropdown {
background-color: #f8f9fa;
border-bottom: 2px solid rgb(228, 228, 228);
position: relative;
}
Solution: I created for the useState, useEffect and stickNavbar corresponding items:
const [scroll, setScroll] = useState(0);
React.useEffect(() => {
window.addEventListener('scroll', stickNavbar);
return () => window.removeEventListener('scroll', stickNavbar);
}, []);
const scrollPos = () => {
if (window !== undefined) {
let posHeight = window.scrollY;
setScroll(posHeight)
}
};
than I used them to follow the change in the scroll Y coordinates to set the distance of the position + rem in pixels of the height of the navbar:
<Container fluid className="dropdown-container" style=
{{top:`${112+scroll}`+"px"}}>
I want to synchronize a divs scroll with a body scroll.
I tried some examples with two divs but I couldn't manage fix it with the body scroll.
Sample code with two divs: https://codesandbox.io/s/react-custom-scroll-sync-of-2-divs-10xpi
My Code
https://codesandbox.io/s/funny-rain-ditbv
import "./styles.css";
import { useRef } from "react";
export default function App() {
const firstDivRef = useRef();
const secondDivRef = useRef();
const handleScrollFirst = (scroll) => {
secondDivRef.current.scrollTop = scroll.target.scrollTop;
};
const handleScrollSecond = (scroll) => {
firstDivRef.current.scrollTop = scroll.target.scrollTop;
};
return (
<div
className="App"
style={{
display: "flex",
}}
>
<div
onScroll={handleScrollFirst}
ref={firstDivRef}
style={{
height: "500px",
overflow: "scroll",
backgroundColor: "#FFDAB9",
position: "sticky",
top: "0px"
}}
>
<div style={{ height: 5000, width: 300 }}>
The first div (or it can be tbody of a table and etc.)
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
<div
onScroll={handleScrollSecond}
ref={secondDivRef}
style={{
height: "100%",
backgroundColor: "#EEE8AA"
}}
>
<div style={{ height: 5000, width: 200 }}>
The second div
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
</div>
);
}
It was easy to use different divs rather than using a div and window.
But finally managed to run it with a div and the body.
The trick is they block each other since they listen each others values.
import "./styles.css";
import { useEffect, useRef, useState } from "react";
export default function App() {
const firstDivRef = useRef();
const [scrollTop, setScrollTop] = useState(0);
const [disableBodyScroll, setDisableBodyScroll] = useState(false);
const handleScrollFirst = (scroll) => {
setScrollTop(scroll.target.scrollTop);
};
useEffect(() => {
if (firstDivRef.current && !disableBodyScroll) {
firstDivRef.current.scrollTop = scrollTop;
}
if (disableBodyScroll) {
window.scrollTo(0, scrollTop);
}
}, [firstDivRef, scrollTop, disableBodyScroll]);
useEffect(() => {
const onScroll = () => {
console.log(disableBodyScroll, window.scrollY);
if (!disableBodyScroll) {
setScrollTop(window.scrollY);
}
};
// clean up code
window.removeEventListener("scroll", onScroll);
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, [disableBodyScroll]);
return (
<div
className="App"
style={{
display: "flex"
}}
>
<div
onMouseEnter={() => setDisableBodyScroll(true)}
onMouseLeave={() => setDisableBodyScroll(false)}
onScroll={handleScrollFirst}
ref={firstDivRef}
style={{
height: "500px",
overflow: "scroll",
backgroundColor: "#FFDAB9",
position: "sticky",
top: "0px"
}}
>
<div style={{ height: 5000, width: 300 }}>
The first div (or it can be tbody of a table and etc.)
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
<div
style={{
height: "100%",
backgroundColor: "#EEE8AA"
}}
>
<div style={{ height: 5000, width: 200 }}>
The second div
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
</div>
);
}
https://codesandbox.io/s/ancient-dream-tzuel?file=/src/App.js
Try the next example. This is a quick sketch but maybe it will help you.
https://codesandbox.io/s/gallant-goldwasser-19g4d?file=/src/App.js
Hello fellow react developers!
I am trying to make a list of items, with two basic operations: add item and remove item.
What I want to do is whenever I add an item, I want it to have a nice fade in effect (newly added item fades in), and when I remove it, I want it to fade-out (the removed item fades out).
What would be the simplest or most straightforward way of achieving this effect on the given implementation (or if the implementation needs to be adjusted to do it, that works as well :))?
function App() {
const [items, setItems] = useState([]);
return (
<div>
<button
className='button-add'
onClick={() => setItems([...items, {
id: new Date().getUTCMilliseconds().toString()}])}
>
Add item
</button>
{items.map(item => (
<div className='item'>
<span className='item-name'>{item.id}</span>
<button className='button-remove' onClick={() => setItems(items.filter((itemInner) => itemInner.id !== item.id))}>Remove item</button>
</div>
))}
</div>
)
}
Working example (add/remove) on codepen
Keyframes are what you are looking for.
I came up with this solution, you might need to tweak it to fit your needs:
Css file:
.item {
-webkit-animation: fadein .3s linear forwards;
animation: fadein .3s linear forwards;
padding: 10px;
}
.item-fadeout{
display: flex;
align-items: center;
padding: 10px;
-webkit-animation: fadeout .3s linear forwards;
animation: fadeout .3s linear forwards;
}
#-webkit-keyframes fadein {
0% { opacity: 0; }
100% { opacity: 1; }
}
#keyframes fadein {
0% { opacity: 0; }
100% { opacity: 1; }
}
#-webkit-keyframes fadeout {
0% { opacity: 1; }
100% { opacity: 0; }
}
#keyframes fadeout {
0% { opacity: 1; }
100% { opacity: 0; }
}
JS:
const { useState } = React;
function Item(props) {
const [isFadingOut, setIsFadingOut] = useState(false);
const fadeOut = (cb) => {
setIsFadingOut(true);
cb();
};
const handleRemoveItem = () => {
props.removeItem();
setIsFadingOut(false);
};
return (
<div className={isFadingOut ? 'item-fadeout' : 'item'}>
<span className='item-name'>{props.item.id}</span>
<button
className='button-remove'
onClick={() => fadeOut(setTimeout(() => handleRemoveItem(), 300))}
>
Remove item
</button>
</div>
);
}
function App() {
const [items, setItems] = useState([]);
return (
<div>
<button
className='button-add'
onClick={() =>
setItems([
...items,
{
id: new Date().getUTCMilliseconds().toString(),
},
])
}
>
Add item
</button>
{items.map((item) => (
<Item
item={item}
removeItem={() =>
setItems(items.filter((itemInner) => itemInner.id !== item.id))
}
/>
))}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'));
Working sample: https://codepen.io/luismendes535/pen/YzyJXdR
I'm currently trying to do the Tic Tac Toe project using hooks. I'm having problems with implementing the "history" functionality:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.scss";
const App = props => {
const [turn, setTurn] = useState("X");
const [history, setHistory] = useState([Array(9).fill(null)]);
const [winner, setWinner] = useState(null);
const latestBoard = history[history.length - 1];
const handleClick = i => {
if (latestBoard[i] !== null) {
return;
}
latestBoard.splice(i, 1, turn);
setHistory([...history, [...latestBoard]]);
setTurn(turn === "O" ? "X" : "O");
};
const handleHistory = index => {
if (index === 1) {
setHistory([Array(9).fill(null)]);
return;
}
let newHistory = history.slice(0, index);
setHistory(newHistory);
console.log(history);
};
const calcWinner = squares => {
/*...*/
};
return (
<div className="container">
<GameBoard onClick={idx => handleClick(idx)} squares={latestBoard} />
<Sidebar
winner={winner}
turn={turn}
history={history}
goBack={idx => handleHistory(idx)}
/>
</div>
);
};
const GameBoard = ({ squares, onClick }) => {
const grid = squares.map((item, idx) => {
return (
<div key={idx} onClick={() => onClick(idx)}>
{item}
</div>
);
});
return <div className="grid-container">{grid}</div>;
};
const Sidebar = ({ winner, turn, history, goBack }) => {
let historyList = history.map((pos, idx) => {
return idx >= 1 ? (
<li key={idx} onClick={() => goBack(idx)}>
{idx}
</li>
) : null;
});
let info;
if (winner) {
info = `The winner is ${winner}`;
} else {
info = `${turn} it's your turn`;
}
return (
<div className="sidebar">
<div className="gameInfo">{info}</div>
<ul className="history-dropdown"> Go to move {historyList}</ul>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Every time I click a list item to call handleHistory it doesn't take effect until the second click
I have a code sample here: https://codesandbox.io/embed/q75y24qj49?fontsize=14
There are two small issues, that need to be fixed. You got React part correct. The issues are :
In your handle click, you need to create a new array, instead of using the old one.
slice params need to be different in handle history to get desired output.
Have added comments in your code for understanding. Hope it will be helpful to you.
const { useState, useEffect } = React
const App = props => {
const [turn, setTurn] = useState("X");
const [history, setHistory] = useState([Array(9).fill(null)]);
const [winner, setWinner] = useState(null);
const latestBoard = history[history.length - 1];
const handleClick = i => {
if (latestBoard[i] !== null) {
return;
}
//latestBoard.splice(i, 1, turn);
//setHistory([...history, [...latestBoard]]);
// latestBoard and history are same
// reference hence you are spreading the same array
// you need to do something like this below
const copylatestBoard = [...latestBoard]; // Because array are passed by
// reference 1st change
copylatestBoard.splice(i, 1, turn); // 2nd change
setHistory([...history, [...copylatestBoard]]); //3rd change
setTurn(turn === "O" ? "X" : "O");
};
const handleHistory = index => {
// if (index === 1) {
// setHistory([Array(9).fill(null)]);
// return;
// }
let newHistory = history.slice(0, index + 1); // need to be index + 1 to slice desired
// as history at 0 is blank board. I am sure it will be obvious now
setHistory(newHistory);
};
const calcWinner = squares => {
/*...*/
};
return (
<div className="container">
<GameBoard onClick={idx => handleClick(idx)} squares={latestBoard} />
<Sidebar
turn={turn}
history={history}
goBack={idx => handleHistory(idx)}
/>
</div>
);
};
const GameBoard = ({ squares, onClick }) => {
const grid = squares.map((item, idx) => {
return (
<div key={idx} onClick={() => onClick(idx)}>
{item}
</div>
);
});
return <div className="grid-container">{grid}</div>;
};
const Sidebar = ({ winner, turn, history, goBack }) => {
let historyList = history.map((pos, idx) => {
return idx >= 1 ? ( // to start after the first click
<li key={idx} onClick={() => goBack(idx)}>
{idx}
</li>
) : null;
});
let info;
if (winner) {
info = `The winner is ${winner}`;
} else {
info = `${turn} it's your turn`;
}
return (
<div className="sidebar">
<div className="gameInfo">{info}</div>
<ul className="history-dropdown"> Go to move {historyList}</ul>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
body {
font-family: sans-serif;
text-align: center;
display: flex;
justify-content: center;
}
.container {
border: 1px solid black;
width: 25em;
height: 90vh;
max-height: 18em;
display: flex;
justify-content: center;
}
.container > * {
border: 1px solid black;
margin: 1em;
}
.grid-container {
flex: 5;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
grid-template-areas: ". . ." ". . ." ". . .";
position: relative;
height: 80%;
}
.grid-container > * {
border: 1px solid black;
}
.grid-container > *:hover {
cursor: pointer;
}
.sidebar {
padding: 1em;
}
.sidebar .history-dropdown {
transition: all 600ms ease-in-out;
}
.sidebar .history-dropdown > *:hover {
cursor: pointer;
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>