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
className
string passed in with those added by the class - Collects the
activecard
from the component state object to inform the nav elements / cards which is currently active / visible - In the return:
- Create the wrapping
Carousel
element 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
Carousel
to create navigation elements. The active nav element is styled as active when thecardindex
matches theactivecard
. - A
react-swipeable-views
instance,SwipeableViews
, is added to enclose the child cards passed to theCarousel
.SwipeableViews
enables the swiping of cards into view in addition to interacting with the nav elements. - We loop over the child nodes, this time calling
React.cloneElement
in order to add a few props likeclassName
,cardindex
, andactivecard
to the original nodes that were passed in. ThecloneElement
method 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 theSwipeableViews
parent. The card whosecardindex
matches theactiveitem
is 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…