This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.
The carousel component, popularized in Sencha Touch and now available in Ext JS in the modern toolkit, is similar to the tab panel in that views in a card layout are shown and hidden using a navigation bar. Unlike the tab panel, carousel navigation is simplified by dropping text and icons in favor of a simple nav element like a circle with “active” styling to show which child item in the card array is in view. In addition to interacting with the nav elements you can swipe to reveal neighboring cards in a carousel view.
Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.
React Carousel Class
Let’s look at an example of the carousel view in React. We’ll start by defining a Carousel class. First, we’ll need to install the react-swipeable-views package:
npm install --save react-swipeable-views
The react-swipeable-views package enables the animated card-swapping action as you navigate between cards using the dot indicators as well as dragging / swiping between cards. Users of the React Material UI library may recognize its use from the “Swipeable example” in the tabs demo.
import React, { Component } from 'react';
import SwipeableViews from 'react-swipeable-views';
import './Carousel.css';
class Carousel extends Component {
static defaultProps = {
activecard: 0,
className: '',
position: 'bottom'
}
state = {
activecard: this.props.activecard
}
render () {
let { className } = this.props;
className = className ? ` ${className}` : '';
const { children, position } = this.props;
const { activecard } = this.state;
const xPositions = ['top', 'bottom'],
axis = xPositions.includes(position) ? 'x' : 'y';
return (
<div
{...this.props}
className = {`carousel ${position}${className}`}
>
<div className={`nav-strip`}>
{React.Children.map(children, (child, i) => {
const isActive = (i === activecard) ? 'active' : '';
return <div
onClick={this.onNavClick.bind(this, i)}
className={`nav ${isActive}`}
>
<span className="nav-dot"></span>
</div>;
})}
</div>
<SwipeableViews
index={activecard}
onChangeIndex={this.onNavClick.bind(this)}
enableMouseEvents={true}
axis={axis}
>
{React.Children.map(children, (child, i) => {
let { className } = child.props;
className = className ? ` ${className}` : '';
const isActive = (i === activecard) ? ' active' : '';
const cardProps = {
...child.props,
style : {flex: 1},
className : ` card${isActive}${className}`,
cardindex : i,
activecard
};
return React.cloneElement(child, cardProps);
})}
</SwipeableViews>
</div>
);
}
onNavClick (activecard) {
this.setState({
activecard
});
}
}
export default Carousel;
React Carousel Class Explained
Above the class definition, we’re importing react-swipeable-views which we’ll use to wrap the child card items in the render method (described below). The static defaultProps property sets the defaults for various props on the Carousel. The constructor method sets the initial state and activecard property and the onNavClick method handles the nav element click that sets the activecard property which then styles the active nav element and shows the associated child card. The render method:
- Combines any
classNamestring passed in with those added by the class - Collects the
activecardfrom the component state object to inform the nav elements / cards which is currently active / visible - In the return:
- Create the wrapping
Carouselelement that will house the nav element container and card container - The nav element container is added and we iterate over the child nodes (cards) passed to the
Carouselto create navigation elements. The active nav element is styled as active when thecardindexmatches theactivecard. - A
react-swipeable-viewsinstance,SwipeableViews, is added to enclose the child cards passed to theCarousel.SwipeableViewsenables the swiping of cards into view in addition to interacting with the nav elements. - We loop over the child nodes, this time calling
React.cloneElementin order to add a few props likeclassName,cardindex, andactivecardto the original nodes that were passed in. ThecloneElementmethod allows us to effectively extend the child items by taking on additional props as needed. The cloned elements are returned in an array to be the child nodes of theSwipeableViewsparent. The card whosecardindexmatches theactiveitemis shown while the other cards are hidden using CSS rules.
- Create the wrapping
React Carousel CSS
The CSS used to render the Carousel view:
.carousel {
position: relative;
}
.nav-strip {
display: flex;
justify-content: center;
position: absolute;
pointer-events: none;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.nav-strip + div,
.nav-strip + div .react-swipeable-view-container {
height: 100%;
width: 100%;
}
.carousel.top .nav-strip {
bottom: auto;
}
.carousel.bottom .nav-strip {
top: auto;
}
.carousel.left .nav-strip {
right: auto;
}
.carousel.right .nav-strip {
left: auto;
}
.carousel.top .nav-strip,
.carousel.bottom .nav-strip {
flex-direction: row;
}
.carousel.left .nav-strip,
.carousel.right .nav-strip {
flex-direction: column;
}
.react-swipeable-view-container > div {
flex-basis: 100%;
background: #f7f7f7;
}
.nav {
text-align: center;
cursor: pointer;
pointer-events: all;
}
.carousel.top .nav,
.carousel.bottom .nav {
padding: 12px 6px;
}
.carousel.left .nav,
.carousel.right .nav {
padding: 6px 12px;
}
.nav-strip .nav-dot {
background-color: #d2d2d2;
border-radius: 50%;
height: 12px;
width: 12px;
display: inline-block;
}
.nav-strip .nav:hover .nav-dot {
background-color: #b5b5b5;
}
.nav-strip .nav.active .nav-dot {
background-color: #1e8bfb;
}
.carousel .card {
padding: 12px;
}
React Carousel Example
We can create a Carousel instance like:
import React, { Component } from 'react';
import Carousel from './Carousel';
class App extends Component {
render() {
return (
<Carousel style={{ height: '400px', width: '600px' }}>
<div>Content for the first panel</div>
<div>... and the second panel</div>
</Carousel>
);
}
}
export default App;
We pass in the style prop to give the rendered component explicit dimensions. We can pass a position prop to position the navigation indicators on the “top” or “bottom”. An activecard prop can also be passed to designate the initially active card view.
Conclusion
Hopefully the example demonstrates how easy it will be to get a carousel view built for your React applications. The example is relatively basic, but gets the job done for the most common use cases. It would be fairly easy to enhance the example and allow the carousel instance to stipulate whether the carousel is oriented horizontally as shown, or vertical. However, if you’re looking for a pre-built slider, look no further than react-slick for a very performant and highly configurable carousel view.
Mitchell Simoens
Related Posts
-
Ext JS to React: Migration to Open Source
Worried about Migrating from Ext JS? Modus has the Answers Idera’s acquisition of Sencha has…
-
Ext JS to React: FAQ
This is part of the Ext JS to React blog series. React is Facebook's breakout…

