When using react-spring it is possible to animate items as they are added / removed from an array. Now, I have an array with a fixed length where all items are null by default. When a value changes to an object, I fade in the component.
This works fine except when changing the value back to null. When unmounting, react-spring duplicates the component untill the animation of the original component is finished.
Is there a way to keep this duplicating from happening?
I made an example at https://codepen.io/AndriesVDW/pen/OJErMjv
<div id="app"></div>
.tiles {
display: flex;
gap: 10px;
list-style: none;
margin: 10px;
padding: 0
}
.tile {
height: 100px;
width: 100px;
border: 1px solid black;
border-radius: 10px;
}
.card {
height: 100%;
width: 100%;
background: red;
border-radius: 10px;
}
const { useState } = React;
const { createRoot } = ReactDOM;
const { animated, useTransition } = ReactSpring;
const App = () => {
return (
<div>
<Grid />
</div>
);
};
const Grid = () => {
const [items, setItems] = React.useState([null, null, null]);
const transition = useTransition(items, {
from: { opacity: 0, transform: "translateY(-50px)" },
enter: { opacity: 1, transform: "translateY(0px)" },
leave: { opacity: 0, transform: "translateY(-50px)" }
});
const add = () => {
setItems(["Card 1", null, null]);
};
const remove = () => {
setItems([null, null, null]);
};
return (
<div>
<button onClick={ add }>Add card to first slot</button>
<button onClick={ remove }>Remove card in first slot</button>
<ul className="tiles">
{ transition((style, item) => (
<li className="tile">
{ item && (
<animated.div className="card" style={ style }>{ item }</animated.div>
) }
</li>
)) }
</ul>
</div>
);
};
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<App />);
Related
I'm new to react and wonder how to do weird code stuff. I have a div component that I need to add child divs to depending on where I clicked on the div. I could do this easily in vanilla JS - here is a code sandbox of JS of what I want to do : https://codepen.io/Webasics/pen/YXXyEO
here is what I have in react so far (this is inside my App component):
const imgAdder = (e) => {
console.log(e.pageX, e.pageY)
}
<main onClick={imgAdder} </main>
$(document).ready(function() {
$(this).click(function(e) {
var x = e.pageX;
var y = e.pageY;
$('<div/>').css({
'top': y,
'left': x
}).appendTo('body');
});
});
div {
background-color: red;
width: 50px;
height: 50px;
position: absolute;
transform: translate(-50%, -50%);
/* optional */
border: 1px solid black;
/* optional */
}
h2 {
z-index: 10;
/* optional */
/* This always keeps the title on top*/
position: absolute;
}
body {
background-color: #E1E7E8;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h2>Click anywhere</h2>
Any directions would be lovely ! thank you.
function App() {
// declare array of boxes
const [boxes, setBoxes] = useState([]);
const handleClick = ({ pageX, pageY }) => {
// on every click push a new coordinate to the boxes array
setBoxes((boxes) => [...boxes, { x: pageX, y: pageY }]);
};
return (
<div className="app" onClick={handleClick}>
// display boxes
{boxes.map((box) => (
// map coordinates to left and top
<div className="box" style={{ left: box.x, top: box.y }}></div>
))}
</div>
);
}
Styles, mostly copied from the codepen
.app {
width: 100%;
height: 100vh;
}
.box {
position: absolute;
width: 50px;
height: 50px;
background: red;
transform: translate(-50%, -50%);
}
sandbox
Weird, but I like it!
https://codesandbox.io/s/elated-meadow-zuerrg?file=/src/App.js
I would simply use useEffect to register a click handler on the document and on click, add elements to a state array.
Finally, render those elements onto the page.
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const elements = useDynamicElements();
return (
<>
<h2>Click anywhere</h2>
{elements}
</>
);
}
const useDynamicElements = () => {
const [state, setState] = useState([]);
useEffect(() => {
const handler = (event) => {
setState((previous) => [
...previous,
<div style={{ top: event.pageY, left: event.pageX }} />
]);
};
document.addEventListener("click", handler);
return () => document.removeEventListener("click", handler);
});
return state;
};
An over simplified example in React could be like this:
This version can run in the snippets below for convenience.
const App = () => {
const [boxList, setBoxList] = React.useState([]);
const handleClick = (e) => {
if (e.target.classList.contains("btn")) {
setBoxList([]);
return;
}
setBoxList((prev) => {
const { pageX, pageY } = e;
const newBox = { left: pageX, top: pageY };
return [...prev, newBox];
});
};
return (
<div className="app" onClick={handleClick}>
<button className="btn">CLEAN UP</button>
<h2>Click anywhere</h2>
{boxList.length > 0 &&
boxList.map((box, index) => (
<div className="box" style={{ top: box.top, left: box.left }} key={index}></div>
))}
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#root"));
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.app {
width: 100%;
height: 100vh;
background-color: pink;
position: relative;
}
.box {
background-color: #000;
width: 50px;
height: 50px;
position: absolute;
transform: translate(-50%, -50%);
border: 1px solid black;
}
h2 {
top: 50%;
left: 50%;
position: absolute;
transform: translate(-50%, -50%);
}
.btn {
margin: 15px;
padding: 15px;
background-color: #fff;
border: 0;
border-radius: 12px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>
I'm trying to implement data-rendered resizable components using the example below (first code snippet). I tried writing it with useRef but that would require putting a hook inside a loop which is against React's documentation.
This is the snippet that contains desired functionality.
const App = () => {
const ref = React.useRef(null);
const refRight = React.useRef(null);
React.useEffect(() => {
const resizeableEle = ref.current;
const styles = window.getComputedStyle(resizeableEle);
let width = parseInt(styles.width, 10);
let x = 0;
resizeableEle.style.top = '20px';
resizeableEle.style.left = '20px';
// Right resize
const onMouseMoveRightResize = (event) => {
const dx = event.clientX - x;
x = event.clientX;
width = width + dx;
resizeableEle.style.width = `${width}px`;
};
const onMouseUpRightResize = () => {
document.removeEventListener('mousemove', onMouseMoveRightResize);
};
const onMouseDownRightResize = (event) => {
x = event.clientX;
resizeableEle.style.left = styles.left;
resizeableEle.style.right = null;
document.addEventListener('mousemove', onMouseMoveRightResize);
document.addEventListener('mouseup', onMouseUpRightResize);
};
// Add mouse down event listener
const resizerRight = refRight.current;
resizerRight.addEventListener('mousedown', onMouseDownRightResize);
return () => {
resizerRight.removeEventListener('mousedown', onMouseDownRightResize);
};
}, []);
return (
<div className="container">
<div ref={ref} className="resizeable">
<p>Hello this is text</p>
<div ref={refRight} className="resizer resizer-r"></div>
</div>
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById("root")
);
.container {
border-radius: 5px;
width: 100vw;
height: 100vh;
background: #FFBC97;
position: relative;
}
.resizeable {
position: absolute;
top: 20px;
left: 150px;
border: 2px solid #533535;
width: 100px;
height: 100px;
border-radius: 3px;
display:block;
/* justify-content: center; */
align-items: center;
min-width: 15px;
min-height: 15px;
text-overflow: ellipsis !important;
white-space: nowrap !important;
overflow: hidden !important;
}
.resizer {
position: absolute;
background: black;
}
.resizer-r {
cursor: col-resize;
height: 100%;
right: 0;
top: 0;
width: 2px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
Basically I'm trying to reproduce the same resize functionality but instead of using useRef I want to define event listeners inline like this <div onMouseMove={handleMouseMove}>. Mainly because having hooks inside a loop is not a good practie in React.
I want it to look somewhat like the second snipped below, Is this the right approach?
const App = () => {
const data = ['item1', 'item2', 'item3', 'item4', 'item5'];
return (
<div className="App">
{data.map((x, i) => {
function handleMouseDown(e) {
console.log('trying to resize');
}
function handleMouseUp() {
console.log('handling mouseUp');
}
const onMouseMoveRightResize = (e) => {
console.log('moving...');
};
return (
<div className="item" key={data[i]}>
{x}
<div key={`i${i}`} className="resizer" onMouseMove={onMouseMoveRightResize} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp}></div>
</div>
);
})}
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById("root")
);
.App {
text-align: center;
}
.item{
position: relative;
border: 1px solid black;
width: 300px;
margin: 10px auto;
border-radius: 3px;
cursor: pointer;
}
.resizer {
position: absolute;
background: black;
cursor: col-resize;
height: 100%;
right: 0;
top: 0;
width: 5px;
}
<div id="root"></div>
<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>
I tried reproducing that many times. I managed to change the widths of the components but I keep failing at implementing smooth resize functionality. Can anyone help me, please?
So to make sure we're answering this correctly - first of if your intention is to render this same resizable component multiple times you can just move that logic to another component and rerender it as shown in this codesandbox.
In case you wish to do something custom for each item, for these 3 functions function handleMouseDown, handleMouseUp, onMouseMoveRightResize then we could just pass these functions to the child, and expect the child to pass in the required props and handle the change - additionally from the index we know which item got clicked, resized, released. Codesandbox link (Open the console and the logs should make it clear).
So the gist of it is you can create another component which can be rendered in the map function circumventing the requirement to not use hooks directly in map functions.
App.js
import "./styles.css";
import ResizableComponent from "./ResizableComponent";
const App = () => {
const data = ["item1", "item2", "item3", "item4", "item5"];
return (
<div className="container">
{data.map((x, index) => (
<ResizableComponent index={index} key={x} />
))}
</div>
);
};
export default App;
ResizableComponent.js
import { useEffect, useRef } from "react";
const ResizableComponent = ({
index,
handleMouseDown,
handleMouseUp,
handleMouseMoveRightResize
}) => {
const ref = useRef(null);
const refRight = useRef(null);
useEffect(() => {
const resizeableEle = ref.current;
const resizerRight = refRight.current;
if (resizeableEle && resizerRight) {
const styles = window.getComputedStyle(resizeableEle);
// let width = parseInt(styles.width, 10);
// let x = 0;
resizeableEle.style.top = `${index * 120 + 20}px`;
resizeableEle.style.left = "20px";
// Right resize
const onMouseMoveRightResize = (event) => {
// const dx = event.clientX - x;
// x = event.clientX;
// width = width + dx;
// resizeableEle.style.width = `${width}px`;
handleMouseMoveRightResize();
// pass whatever props you want to the parent component
};
const onMouseUpRightResize = () => {
handleMouseUp();
document.removeEventListener("mousemove", onMouseMoveRightResize);
};
const onMouseDownRightResize = (event) => {
handleMouseDown();
// pass wahtever props you want to the parent component
// x = event.clientX;
// resizeableEle.style.left = styles.left;
// resizeableEle.style.right = '';
document.addEventListener("mousemove", onMouseMoveRightResize);
document.addEventListener("mouseup", onMouseUpRightResize);
};
resizerRight.addEventListener("mousedown", onMouseDownRightResize);
return () => {
if (resizerRight)
resizerRight.removeEventListener("mousedown", onMouseDownRightResize);
};
}
}, [handleMouseDown, handleMouseMoveRightResize, handleMouseUp, index]);
return (
<div ref={ref} className="resizeable">
<p>Hello this is text</p>
<div ref={refRight} className="resizer resizer-r"></div>
</div>
);
};
export default ResizableComponent;
Also by uncommenting the code in the second solution you can have the default resize functionality and additionally do something else in the handleMouseDown, handleMouseUp, handleMouseMoveRightResize functions.
I will try to find a more viable solution if there's one but I think this should do it.
I have a react-spring animation that consists in make a component appear while sliding. But during this animation, the component change its margin top before going back to normal. How to fix this?
Here is the code & a sandbox:
import React, { useEffect, useMemo, useState } from "react";
import styled, { css, keyframes } from "styled-components";
import { animated, useTransition } from "react-spring";
const Tabs = styled.div`
display: flex;
margin-bottom: 12px;
`;
const Tab = styled.button<{ active: boolean }>`
margin: 0 4px;
border-bottom: 1px solid transparent;
font-weight: ${({ active }) => active && 600};
cursor: pointer;
background: transparent;
border: 0;
&:focus {
outline: none !important;
}
`;
export default function Inbox() {
const [tab, setTab] = useState(0);
const transitions = useTransition(tab, (p) => p, {
from: { opacity: 0, transform: "translate3d(100%,0,0)" },
enter: { opacity: 1, transform: "translate3d(0%,0,0)" },
leave: { opacity: 0, transform: "translate3d(-50%,0,0)" }
});
const getList = (name: string) => <div>{name}</div>;
const pages = [
({ style }) => (
<animated.div style={style}>{getList("test 1")}</animated.div>
),
({ style }) => (
<animated.div style={style}>{getList("test 2")}</animated.div>
)
];
return (
<>
<Tabs>
<Tab onClick={() => setTab(0)} active={tab === 0}>
Unread
</Tab>
<Tab onClick={() => setTab(1)} active={tab === 1}>
All
</Tab>
</Tabs>
{transitions.map(({ item, props, key }) => {
const Page = pages[item];
return <Page key={key} style={props} />;
})}
</>
);
}
The sandbox: https://codesandbox.io/s/amazing-ride-hwbv2?file=/src/App.tsx
It is not the margin top you see. It is the old component moving to the left side and changing opacity, but it is still there. Finally it is unmounted in that moment the new component is taking its vertical place. Most of the time we use absolute positionig that the old and new components are on top of each other. This way there is no bump at the unmount. Something like this:
from: { opacity: 0, transform: "translate3d(100%,0,0)", position: "absolute" },
I am learning to make custom Carousel by using React and Typescript. For styling I used styled components and scss. I found from one Article how to make Carousel. My carousel works fine.
I have made four div elements. when the carousel image slide will change, I want to change background-color of the div elements inot orange color. But Don't know how to do that.
I share my code in codesandbox
This is my Carousel component
import React, { useState, useEffect, useRef, memo, useCallback } from "react";
import styled from "styled-components";
interface ICarousel {
children: JSX.Element[];
currentSlide?: number;
autoPlay?: boolean;
dots?: boolean;
interval?: number;
arrow?: boolean;
}
const IMG_WIDTH = 320;
const IMG_HEIGHT = 700;
export default memo(
({
children,
autoPlay = false,
dots = false,
interval = 3000,
arrow = false
}: ICarousel) => {
const [currentSlide, setSlide] = useState(0);
const [isPlaying, setIsPlaying] = useState(autoPlay);
const timer = useRef<any>(undefined);
const slides = children.map((slide, index) => (
<CarouselSlide key={index}>{slide}</CarouselSlide>
));
const handleSlideChange = useCallback(
(index: number) =>
setSlide(
index > slides.length - 1 ? 0 : index < 0 ? slides.length - 1 : index
),
[slides]
);
const createInterval = useCallback(() => {
timer.current = setInterval(() => {
handleSlideChange(currentSlide + 1);
}, interval);
}, [interval, handleSlideChange, currentSlide]);
const destroyInterval = useCallback(() => {
clearInterval(timer.current);
}, []);
useEffect(() => {
if (autoPlay) {
createInterval();
return () => destroyInterval();
}
}, [autoPlay, createInterval, destroyInterval]);
return (
<CarouselContainer
onMouseEnter={() => {
if (autoPlay) {
destroyInterval();
}
}}
onMouseLeave={() => {
if (autoPlay) {
createInterval();
}
}}
>
<CarouselSlides currentSlide={currentSlide}>{slides}</CarouselSlides>
{arrow ? (
<div>
<LeftButton onClick={() => handleSlideChange(currentSlide - 1)}>
❮
</LeftButton>
<RightButton onClick={() => handleSlideChange(currentSlide + 1)}>
❯
</RightButton>
</div>
) : null}
{dots ? (
<Dots>
{slides.map((i, index) => (
<Dot
key={index}
onClick={() => handleSlideChange(index)}
active={currentSlide === index}
/>
))}
</Dots>
) : null}
</CarouselContainer>
);
}
);
const Buttons = styled.a`
cursor: pointer;
position: relative;
font-size: 18px;
transition: 0.6s ease;
user-select: none;
height: 50px;
width: 40px;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
top: calc(50% - 25px);
position: absolute;
&:hover {
background-color: rgba(0, 0, 0, 0.8);
}
`;
const RightButton = styled(Buttons)`
border-radius: 3px 0 0 3px;
right: 0;
`;
const LeftButton = styled(Buttons)`
border-radius: 0px 3px 3px 0px;
left: 0;
`;
const Dots = styled.div`
display: flex;
justify-content: center;
align-items: center;
align-content: center;
margin-top: 10px;
`;
const Dot = styled.span<{ active: boolean }>`
cursor: pointer;
height: 15px;
width: 15px;
margin: 0 10px;
border-radius: 50%;
display: inline-block;
transition: background-color 0.6s ease;
background-color: ${({ active }) => (active ? `red` : `#eeeeee`)};
`;
const CarouselContainer = styled.div`
overflow: hidden;
position: relative;
width: ${IMG_WIDTH}px;
height: ${IMG_HEIGHT}px;
img {
/* change the margin and width to fit the phone mask */
width: ${IMG_WIDTH - 20}px;
height: ${IMG_HEIGHT - 50}px;
margin-left: 10px;
margin-top: 15px;
}
z-index: 1;
`;
const CarouselSlide = styled.div`
flex: 0 0 auto;
transition: all 0.5s ease;
width: 100%;
`;
const CarouselSlides = styled.div<{
currentSlide: ICarousel["currentSlide"];
}>`
display: flex;
${({ currentSlide }) =>
` transform: translateX(-${currentSlide ? currentSlide * 100 + `%` : 0})`};
transition: transform 300ms linear;
cursor: pointer;
`;
This where I am using the Carousel component
import * as React from "react";
import "./styles.scss";
import Carousel from "./carousel";
export const imgUrls = [
`https://images.unsplash.com/photo-1455849318743-b2233052fcff?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=900&q=60`,
`https://images.unsplash.com/photo-1508138221679-760a23a2285b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=900&q=60`,
`https://images.unsplash.com/photo-1519125323398-675f0ddb6308?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=900&q=60`,
`https://images.unsplash.com/photo-1494253109108-2e30c049369b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=900&q=60`
];
export default function App() {
return (
<div className="App">
<main className="Testeru">
<div className="phone-left">
// I want change background color of this div element
<div className="phone-left-upper">left upper</div>
<div className="phone-left-lower">left-lower</div>
</div>
<div className="phone-slider">
<div className="mobile_overlay">
<Carousel autoPlay>
{imgUrls.map((i) => {
return (
<img
key={i}
src={i}
alt=""
style={{
borderRadius: `20px`
}}
/>
);
})}
</Carousel>
</div>
</div>
// I want change background color of this div element
<div className="phone-right">
<div className="phone-right-upper">right upper</div>
<div className="phone-right-lower">right-lower</div>
</div>
</main>
</div>
);
}
I just studied React from YouTube lessons, and there all the lessons were built on classes and the usual this.setState, without hooks. How would this React code look without React-hooks and with class components rather than functional components?
The code itself implements an image slider:
React:
function Slider({ items }) {
const [ active, setActive ] = React.useState(0);
const { length, [active]: slide } = items;
const next = e => setActive((active + +e.target.dataset.step + length) % length);
const goTo = e => setActive(+e.target.dataset.index);
React.useEffect(() => {
const timeout = setTimeout(() => setActive((active + 1 + length) % length), 5000);
return () => clearTimeout(timeout);
}, [active, length]);
return (
<div>
<div className="slideshow-container">
<div className="mySlides fade">
<div className="numbertext">{active + 1} / {length}</div>
<img src={slide.img} />
<div className="text">{slide.title}</div>
</div>
<a className="prev" onClick={next} data-step={-1}>❮</a>
<a className="next" onClick={next} data-step={+1}>❯</a>
</div>
<div className="dots">
{items.map((n, i) => (
<span
key={n.id}
className={`dot ${i === active ? 'active' : ''}`}
onClick={goTo}
data-index={i}
></span>
))}
</div>
</div>
);
}
const items = [
{ title: 'One', img: 'https://upload.wikimedia.org/wikipedia/commons/1/1f/Purity_of_nature.jpg' },
{ title: 'Two', img: 'https://mairie-balma.fr/wp-content/uploads/2016/06/Lhers.jpg' },
{ title: 'Three', img: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRt-b1iBqHQ_emkm1wFmkM7KQskzIqg7YQPZWW85Sa7k2nNLwgjMw' },
].map((n, i) => ({ ...n, id: i + 1 }));
ReactDOM.render(<Slider items={items} />, document.getElementById('app'));
HTML
<div id="app"></div>
CSS:
.slideshow-container {
max-width: 500px;
position: relative;
margin: auto;
}
.prev, .next {
cursor: pointer;
position: absolute;
top: 50%;
width: auto;
padding: 16px;
margin-top: -22px;
color: white;
font-weight: bold;
font-size: 18px;
transition: 0.6s ease;
border-radius: 0 3px 3px 0;
user-select: none;
}
.next {
right: 0;
border-radius: 3px 0 0 3px;
}
.prev:hover, .next:hover {
background-color: rgba(0,0,0,0.8);
}
.text {
color: #f2f2f2;
font-size: 15px;
padding: 8px 12px;
position: absolute;
bottom: 8px;
width: 100%;
text-align: center;
box-sizing: border-box;
}
.numbertext {
color: #f2f2f2;
font-size: 12px;
padding: 8px 12px;
position: absolute;
top: 0;
}
.dots {
display: flex;
justify-content: center;
}
.dot {
cursor: pointer;
height: 15px;
width: 15px;
margin: 0 2px;
background-color: #bbb;
border-radius: 50%;
display: inline-block;
transition: background-color 0.6s ease;
}
.active, .dot:hover {
background-color: #717171;
}
.mySlides img {
width: 100%;
}
Something like this (not fully tested):
class Slider {
constructor(props) {
super(props);
this.state = {
active: 0
}
}
let timeout = null;
componentDidMount() {
this.timeout = setTimeout(() => this.setActive(), 5000);
}
componentDidUpdate(prevProps) {
const { active } = this.props;
if (prevProps.active !=== active {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() => this.setActive(), 5000);
});
}
componentDidUnmount() {
if (this.timeout) {
clearTimeout(this.timeout);
}
}
const setActive = (newActive) => {
const { length } = items;
this.setState({
active: (newActive + 1 + length) % length
});
}
const next = e => {
const { length } = items;
this.setActive((this.state.active + +e.target.dataset.step + length) % length);
}
const goTo = e => this.setActive(+e.target.dataset.index);
render() {
const { length } = items;
const {active} = this.state;
return (
<div>
<div className="slideshow-container">
<div className="mySlides fade">
<div className="numbertext">{active + 1} / {length}</div>
<img src={slide.img} />
<div className="text">{slide.title}</div>
</div>
<a className="prev" onClick={this.next} data-step={-1}>❮</a>
<a className="next" onClick={this.next} data-step={+1}>❯</a>
</div>
<div className="dots">
{items.map((n, i) => (
<span
key={n.id}
className={`dot ${i === active ? 'active' : ''}`}
onClick={this.goTo}
data-index={i}
></span>
))}
</div>
</div>
);
}
}
const items = [
{ title: 'One', img: 'https://upload.wikimedia.org/wikipedia/commons/1/1f/Purity_of_nature.jpg' },
{ title: 'Two', img: 'https://mairie-balma.fr/wp-content/uploads/2016/06/Lhers.jpg' },
{ title: 'Three', img: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRt-b1iBqHQ_emkm1wFmkM7KQskzIqg7YQPZWW85Sa7k2nNLwgjMw' },
].map((n, i) => ({ ...n, id: i + 1 }));
ReactDOM.render(<Slider items={items} />, document.getElementById('app'));