From the Articles

Building a simple slideshow plugin with React

2nd February 2015


I have just recently made the jump into React.js, without much JavaScript experience really, and I was pleasantly surprised how fun and rewarding it was. If you’ve been growing excited about React, especially now that React Native is on its way, and was scouting for an easy, applied tutorial to give React a whirl, this might be the place.

The objective

Being it a learning experience, we’re really not after anything fancy here. The visual side of things won’t matter to us as much. We’ll build a simple slideshow plugin sporting some basic pagination and prev/next buttons. All easily expandable, populated from a JSON file.

I won’t blabber about how great declarative UI engineering is, or how much sense it makes to break up your UI’s front-end to tiny reusable components. Though you might want to look it up in the “More Resources” section towards the end of this article. Instead, I’ll jump to the sauce straight away.

Step 1: Set things up

Download the latest React starter kit, unzip and create a basic index.html in the root of the extracted folder:

<!DOCTYPE html>
<html>
<head>
  <title>Hello React!</title>
  <script src="build/react-with-addons.js"></script>
  <script src="build/JSXTransformer.js"></script>
  <link rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="app"></div>
<script type="text/jsx">
</script>
</body>
</html>

Step 2: Define your slideshow dataset

Lets say we want each slide of our slideshow to have following items:

  • an image
  • a title
  • a subtitle
  • some text
  • an action button

In order to have it all, we first need to create a dataset of the information required to later populate our slides with. I’m talking about text variables, paths to images, any necessary HTML attributes and the like. As such, lets place in the following within our script tag:

// Dataset
var data = [
  {
    id         : "slide1",
    imagePath  : "img1.jpg",
    imageAlt   : "Slide 1 Image",
    title      : "Slide 1",
    subtitle   : "Slide 1 Image SubTitle",
    text       : "Slide 1 Image Text",
    action     : "Slide 1 Image Action",
    actionHref : "href"
  },
  {
    id         : "slide2",
    imagePath  : "img2.jpg",
    imageAlt   : "Slide 2 Image",
    title      : "Slide 2",
    subtitle   : "Slide 2 Image SubTitle",
    text       : "Slide 2 Image Text",
    action     : "Slide 2 Image Action",
    actionHref : "href"
  },
  {
    id         : "slide3",
    imagePath  : "img3.jpg",
    imageAlt   : "Slide 3 Image",
    title      : "Slide 3",
    subtitle   : "Slide 3 Image SubTitle",
    text       : "Slide 3 Image Text",
    action     : "Slide 3 Image Action",
    actionHref : "href"
  },
];

We’ll pass this dataset to state variable that will take care of storing information about our slideshow’s behaviour later on:

// App state
var state = {
  currentSlide: 0,
  data        : data
}

Note how we’re defining initial slide index here too.

Step 3: Create Slideshow component

We’ll create a wrapping component called Slideshow that will later accommodate three subcomponents: Slides, Pagination and Controls (our prev/next buttons).

// Slideshow Component
var Slideshow = React.createClass({
  render: function() {
    return (
      <div className="slideshow">
      </div>
    );
  }
});

Note how we’re using JSX syntax here. Should that be all new, please refer to this article from React.js specification doc.

In order to actually print out our Slideshow on the page, we need to pass it to React’s render function that will insert it within our #app div as shown below:

// Render
function render(state) {
  React.render(
    <Slideshow data={state.data} />,
    document.getElementById('app')
  );
}

render(state);

Note how we’re passing our app state to our render function to make it available to the Slideshow component.

Step 4: Create Slideshow subcomponents

At this stage you should be able see an empty .slideshow div in your page’s markup tree. We can now insert there other desired components: slides, pagination and prev/next controls. We want to keep those separate so we’ll create a set of variables for each of them:

// Slides
var Slides = React.createClass({
  render: function() {
    var slidesNodes = this.props.data.map(function (slideNode, index) {
    var isActive = state.currentSlide === index;
      return (
        <Slide active={isActive} key={slideNode.id} imagePath={slideNode.imagePath} imageAlt={slideNode.imageAlt} title={slideNode.title} subtitle={slideNode.subtitle} text={slideNode.text} action={slideNode.action} actionHref={slideNode.actionHref} />
      );
    });
    return (
      <div className="slides">
        {slidesNodes}
      </div>
    );
  }
});

// Single Slide
var Slide = React.createClass({
  render: function() {
    var classes = React.addons.classSet({
      'slide': true,
      'slide--active': this.props.active
    });
    return (
      <div className={classes}>
        <img src={this.props.imagePath} alt={this.props.imageAlt} />
        <h2>{this.props.title}</h2>
        <h3>{this.props.subtitle}</h3>
        <p>{this.props.text}</p>
        <a href={this.props.actionHref}>{this.props.action}</a>
      </div>
    );
  }
});

There’s a lot going on in here. You can see that:

  • We’re distinguishing single slides (Slide) from their wrapping container (Slides).
  • Slides takes our dataset, maps it and returns a list of single slideNodes, corresponding to the number of our dataset length, to which it passes all necessary properties.
  • Slides map function also checks which slideNode is active and passes that information as active attribute to the <Slide>.
  • <Slide> takes that active state information (we access it with this.props.active) and—with help of React Addons—applies slide--active class accordingly.
// Prev and Next buttons
var Controls = React.createClass({
  togglePrev: function() {
    actions.togglePrev();
  },
  toggleNext: function() {
    actions.toggleNext();
  },
  render: function() {
    return (
      <div className="controls">
        <div className="toggle toggle--prev" onClick={this.togglePrev}>Prev</div>
        <div className="toggle toggle--next" onClick={this.toggleNext}>Next</div>
      </div>
    );
  }
});

The above is our Controls component that accommodates Prev/Next buttons. Note how we’re adding onClick events to both of them and pass in previously defined function aliases. These are not going to do anything yet, we need to link those aliases to actual event handlers. I recommend to do it by defining a global array of state transitions which I called actions:

// State transitions
var actions = {
  toggleNext: function() {
    console.log("something worked");
    var current = state.currentSlide;
    var next = current + 1;
    if (next > state.data.length - 1) {
      next = 0;
    }
    state.currentSlide = next;
    render(state)
  },
  togglePrev: function() {
    console.log("something worked");
    var current = state.currentSlide;
    var prev = current - 1;
    if (prev < 0) {
      prev = state.data.length - 1;
    }
    state.currentSlide = prev;
    render(state);
  },
  toggleSlide: function(id) {
    console.log("something worked");
    var index = state.data.map(function (el) {
      return (
        el.id
      );
    });
    var currentIndex = index.indexOf(id);
    state.currentSlide = currentIndex;
    render(state);
  }
}

We then can populate our Slideshow with Slides and Controls. To do that, simply edit the bits of code from Step 3 as follows:

// Slideshow Component
var Slideshow = React.createClass({
  render: function() {
    return (
      <div className="slideshow">
        <Slides data={this.props.data} />
        <Controls />
      </div>
    );
  }
});

Step 5: Add in Pagination

Similarly to populating our <Slides> with all the slideNodes, we can now proceed to add a simple pagination. We do that by creating… again… a <Pagination> and <Pager> components using the same mapping function we used before.

// Pagination
var Pagination = React.createClass({
  render: function() {
    var paginationNodes = this.props.data.map(function (paginationNode, index) {
      return (
        <Pager id={paginationNode.id} key={paginationNode.id} title={paginationNode.title}  />
      );
    });
    return (
      <div className="pagination">
        {paginationNodes}
      </div>
    );
  }
});

var Pager = React.createClass({
  toggleSlide: function() {
    actions.toggleSlide(this.props.id);
  },
  render: function() {
    return (
      <span className="pager" onClick={this.toggleSlide}>{this.props.title}</span>
    );
  }
});

We then need to add it in to our Slideshow Component, otherwise it will not be called anywhere:

// Slideshow Component
var Slideshow = React.createClass({
  render: function() {
    return (
      <div className="slideshow">
        <Slides data={this.props.data} />
        <Pagination data={this.props.data} />
        <Controls />
      </div>
    );
  }
});

Step 6: Check if everything is in place

I wrote this tutorial only after I was done and not while I was building the whole thing. As such, I imagine some things could possibly be unclear. Should you have any problems understanding bits and pieces, please refer to a Gist I made for the occassion or download the source package from GitHub—that is surely working and can serve you as reference. Throw in some console.log(this.props) to see how data is being passed along the components. In the end, if you have any questions, just shout out to me on Twitter: @presentday.

More resources

I have recently noted down some React.js resources. You might want to check them out (and maybe subscribe to those weekly posts of mine where I’ll be collecting more cool stuff in the future).


Like what you read? Subscribe via Email or RSS.
Have a comment? Text me on Twitter.